CPython 3.20 弃用前瞻:即将移除的 5 项语言与标准库特性及迁移指南
本文基于 CPython 仓库中 Doc/deprecations/pending-removal-in-3.20.rst 官方弃用公告,系统讲解将在 Python 3.20 中移除的五类特性:struct.Struct 构造函数行为、24 个标准库模块的 __version__ 等版本属性、PEP 829 引入的 .pth 文件行为变化、抽象 AST 节点的直接实例化,以及非显式 runtime_checkable 协议类的 isinstance/issubclass 检查。读完后你将掌握每一项弃用的确切语义、仓库中的源码级实现证据,以及面向 3.20 的完整迁移方案。
弃用公告机制:为什么会有 pending-removal 文档
CPython 的弃用流程通常分为“弃用(deprecation,发出 DeprecationWarning)”和“移除(removal)”两个阶段。Doc/deprecations/ 目录按目标版本归档了每一批待移除的特性,pending-removal-in-3.20.rst 记录的就是“当前开发版已经弃用、将在 3.20 正式移除”的条目。这意味着:这些特性在 3.20 之前的版本中仍可用,但会发出弃用警告;升级路径应以该文件为清单,逐项排查代码。
一、struct.Struct:禁止无 format 参数构造,禁止重复初始化
弃用内容
公告原文包含两条相互关联的弃用(由 Sergey B Kirpichev 和 Serhiy Storchaka 提交):
- 调用
struct.Struct的__new__()方法时不传format参数已被弃用,将在 3.20 移除; - 对已初始化的
Struct对象再次调用__init__()方法已被弃用,将在 3.20 移除。
源码实现证据
Struct 由 C 扩展实现,核心构造逻辑位于 Modules/_struct.c:
s_new()(约 L1883-L1950)负责解析format参数。当未以“恰好一个位置参数”或“单个format=关键字参数”形式提供 format,且类没有自定义__init__覆盖时,源码在 L1899-L1916 显式发出DeprecationWarning:- 参数多于一个时:
"Struct() takes at most 1 argument (N given)"; - 完全缺失时:
"Struct() missing required argument 'format' (pos 1)"。
- 参数多于一个时:
- 重复初始化的弃用位于
Struct___init___impl()(约 L1980-L2003):对象上维护了s_format与init_called两个状态位。若对象已持有 format 且再次以不同格式初始化,会发出警告"Re-initialization of Struct by calling the __init__() method will not work in future Python versions"(L1989-L1996);而s_init()(L2005-L2019)则在“__init__被解释器隐式调用、但__new__已完成全部工作”时直接短路返回,避免重复处理。
迁移方案
正确的构造方式始终是一次性传入 format 字符串,之后视其为不可变的编译产物:
import struct
# 推荐:构造时提供 format(唯一可靠方式)
s = struct.Struct(">I4d")
data = s.pack(1234, 1.5, 2.5, 3.5, 4.5)
values = s.unpack(data)
# 不要依赖以下两种即将移除的行为:
# 1. s2 = struct.Struct() # 无 format 参数构造
# 2. s2 = struct.Struct.__new__(struct.Struct) # 同上
# 3. 对已构造对象再次 s2.__init__("<h") 重新初始化
若确实需要“不同格式”,直接新建 Struct 实例即可——构造开销很低(本质上只是预解析 format 字符串并计算对齐后的 size/len,见 Modules/_struct.c 中 set_format() 的解析流程),无需复用对象。
二、标准库模块级版本属性:__version__ / version / VERSION 全面移除
弃用内容
以下 24 个模块中的 __version__、version 和 VERSION 属性被弃用,将在 3.20 移除,统一改用 sys.version_info(由 Hugo van Kemenade 和 Stan Ulbrych 提交):
| 模块 | 备注 |
|---|---|
| argparse | __version__ 返回硬编码值 |
| csv | |
| ctypes | |
ctypes.macholib |
仅 macOS 可用 |
| decimal | 改用 decimal.SPEC_VERSION |
| http.server | |
| imaplib | |
| ipaddress | |
| json | |
| logging | __date__ 一并弃用 |
| optparse | |
| pickle | |
| platform | |
| re | |
| socketserver | |
| tabnanny | |
| tarfile | |
| tkinter.font | |
| tkinter.ttk | |
| wsgiref.simple_server | |
| xml.etree.ElementTree | |
xml.sax.expatreader |
|
| xml.sax.handler | |
| zlib |
源码实现证据
以 Lib/argparse.py 为例,模块顶层使用 PEP 562 的模块级 __getattr__ 实现惰性弃用(L2972-L2976):
def __getattr__(name):
if name == "__version__":
warnings._deprecated("__version__", remove=(3, 20))
return "1.1" # Do not change
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
注意 warnings._deprecated(..., remove=(3, 20)) 中的 remove=(3, 20) 参数——它生成的警告文案会明确提示“将在 3.20 移除”。Lib/json/init.py(L390-L393)采用完全相同的模式。这种实现还带来一个副作用值得注意:一旦 3.20 移除该属性,访问 argparse.__version__ 将抛出 AttributeError,因此依赖 getattr(module, "__version__", "unknown") 兜底取值的代码会静默降级,而直接属性访问的代码会直接崩溃。
迁移方案
import sys
# 替代 json.__version__ / zlib.VERSION 等所有模块级版本读取
major, minor = sys.version_info[:2]
if sys.version_info >= (3, 12):
...
两个例外需要特别处理:
-
decimal:公告明确指出应改用decimal.SPEC_VERSION,因为它记录的是 Decimal 模块实现的规格版本,与解释器版本无对应关系:import decimal print(decimal.SPEC_VERSION) # 如 (3, 0) -
logging.__date__:一并弃用,如需日志库日期信息请直接读取源码版本控制信息,不要依赖运行时属性。
三、PEP 829:.pth 文件的 import 行与编码要求
弃用内容
这是 PEP 829 在 3.20 落地时的两项具体弃用(由 Barry Warsaw 提交):
.pth文件中的import行:在{name}.pth文件里写 import 语句是旧有的“副作用钩子”,此后每出现此类行都会产生弃用警告;.pth文件编码:默认解码方式从 locale 编码改为强制utf-8-sig(即允许带 BOM 的 UTF-8)。PEP 的措辞是“MUST be encoded in utf-8-sig”,即包含非 ASCII 字符的.pth文件若仍按 locale 编码保存,行为将不再可预期。
背景与迁移
site 模块在启动时扫描 site-packages 中的 .pth 文件:其中的目录行用于扩展 sys.path,而 import 行则会在解释器启动阶段执行任意代码——这正是它被弃用的原因(启动期代码执行难以追踪且影响启动行为)。
迁移建议:
- 不要在
.pth文件中写import xxx。确需在导入包时执行初始化逻辑,应在包自身的__init__.py中完成;确需注册钩子,优先考虑在应用入口或sitecustomize.py中显式处理。 - 若
.pth文件包含中文注释等非 ASCII 内容,请用 UTF-8(带 BOM 更稳妥)重新保存,并在部署后确认site能正常解析,避免在旧 locale 环境下行为不一致。
四、ast:禁止直接实例化抽象 AST 节点
弃用内容
公告指出:创建抽象 AST 节点实例(如 ast.AST、ast.expr 等中间抽象类)已被弃用,将在 3.20 直接抛出错误(不再是警告)。
影响与迁移
ast 模块的类层次中,ast.AST → ast.stmt / ast.expr / ast.keyword 等抽象中间层用于分类,而 ast.Module、ast.FunctionDef、ast.Constant、ast.Name 等具体节点才是可构造对象。3.20 之后:
import ast
# 3.20 之前:可实例化(当前开发版已弃用);3.20 起将抛错
node = ast.expr() # 不要这样写
node = ast.AST() # 不要这样写
# 正确做法:只构造具体节点,或通过解析/转换获得节点树
tree = ast.parse("x = 1")
tree = ast.Module(body=tree.body, type_ignores=[]) # 具体节点,合法
依赖“构造抽象节点作为占位/基类探针”的第三方库或 AST 生成器需要在 3.20 前改为构造具体节点(如用 ast.Constant 代替笼统的 ast.expr 占位)。
五、typing:隐式 runtime-checkable 协议的 isinstance 检查
弃用内容
公告由 Bartosz Sławecki 提交:对未显式用 @runtime_checkable 装饰、但继承自某个 runtime-checkable 协议类的协议类执行 isinstance / issubclass 检查已被弃用,3.20 起将抛出 TypeError。
语义解析
在 typing 中,runtime_checkable 原本可以“隐式继承”——父协议被装饰后,子协议也被动获得运行时检查资格。但隐式资格带来两个问题:一是使用者无法从子类定义处看出它“碰巧可被 isinstance”;二是结构兼容性的运行时检查本身只能核查公有成员存在性,语义弱于显式声明。因此 3.20 的收紧方向是:想要运行时协议检查,就必须在类定义处显式装饰。
from typing import Protocol, runtime_checkable
@runtime_checkable
class Reader(Protocol):
def read(self, n: int = -1) -> bytes: ...
class BufferedReader(Reader):
def read(self, n: int = -1) -> bytes: ...
# 3.20 之前:isinstance(b, Reader) 可用(Reader 隐式继承了 runtime_checkable)
# 3.20 起:将抛 TypeError,除非显式装饰
@runtime_checkable
class BufferedReader(Reader):
def read(self, n: int = -1) -> bytes: ...
迁移策略:对每个需要 isinstance 的协议,显式添加 @runtime_checkable;对只想做静态类型约束的协议,保持不装饰并把运行期 isinstance 替换为显式的接口/类型标注。
小结:面向 3.20 的排查清单
| # | 弃用特性 | 3.20 后果 | 替代方案 |
|---|---|---|---|
| 1 | Struct() 无 format / 对已初始化 Struct 调 __init__ |
移除 | 构造时一次性传入 format,改格式就新建实例 |
| 2 | 24 个标准库模块的 __version__/version/VERSION |
移除,访问抛 AttributeError |
sys.version_info;decimal 用 SPEC_VERSION |
| 3 | .pth 文件 import 行、非 utf-8-sig 编码 |
产生警告 / 解码行为改变 | 移除 import 行;.pth 一律 utf-8-sig 保存 |
| 4 | 抽象 AST 节点(ast.AST、ast.expr)实例化 |
抛出错误 | 只构造具体节点或用 ast.parse |
| 5 | 隐式 runtime-checkable 协议上的 isinstance/issubclass |
抛出 TypeError |
显式 @runtime_checkable 装饰 |
排查手段建议:在当前开发版开启 -X dev(默认显示弃用警告)运行完整测试套件,DeprecationWarning 的文案中带有 remove in 3.20 字样,可直接定位上述五类用法;涉及 C 实现的第 1 项可对照 Modules/_struct.c 中 s_new()(L1883-L1950)与 Struct___init___impl()(L1980-L2003)的警告分支确认具体触发条件,涉及 Python 标准库的第 2 项则可搜索各模块顶层的 def __getattr__(name) 与 warnings._deprecated 调用逐一核对。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00