首页
/ CPython 3.17 待移除清单解析:从 datetime %e 到 profile 模块,7 项弃用 API 的替代方案与源码实现

CPython 3.17 待移除清单解析:从 datetime %e 到 profile 模块,7 项弃用 API 的替代方案与源码实现

2026-09-06 14:38:25作者:何将鹤

CPython 官方在 Doc/deprecations/pending-removal-in-3.17.rst 中列出了计划于 Python 3.17 正式移除的 7 项标准库 API,覆盖 datetimecollections.abcencodingsprofilewebbrowsertypingtkinter 七个模块。本文基于该文档,结合 CPython 仓库中的源码实现,逐项说明每个被弃用 API 的弃用原因、当前版本的行为(DeprecationWarning)、官方推荐的替代写法,以及升级 3.17 前应完成的迁移要点,帮助你写出跨版本兼容的 Python 代码。

总览:3.17 移除清单

模块 待移除内容 弃用起始版本 官方替代方案
datetime strptime 中使用无年份的 %e 格式串 3.15 在输入和格式串中显式补上年份
collections.abc collections.abc.ByteString —(本轮正式排期) collections.abc.Buffer / 显式类型联合
encodings encodings.normalize_encoding 传入非 ASCII 编码名 使用 ASCII 编码名
profile 整个 profile 模块 3.14(PEP 799) profiling.tracing(或 cProfile
webbrowser webbrowser.MacOSXOSAScript webbrowser.MacOS
typing 私有类 typing._UnionGenericAliastyping.ByteString 后者自 3.9 typing.get_origin / typing.get_args / Buffer
tkinter Variable.trace_variable / trace / trace_vdelete / trace_vinfo 3.14 trace_add / trace_remove / trace_info

这些条目中,有的是历史遗留的“空壳”抽象类(如两个 ByteString),有的是实现细节外泄(如 _UnionGenericAlias),还有的是被新一代标准库模块整体替代的旧模块(如 profile)。逐项理解它们,是 3.17 升级前清理 DeprecationWarning 的关键。

datetime:strptime 中无年份的 %e 将变成错误

%estrftime 系列中表示“当月第几天(空格补齐)”的占位符,而 %Y 表示年份。当你在 datetime.datetime.strptime 的格式串中使用 %e不包含年份时,解析行为是存在歧义的:

  • 没有年份就无法判断闰年,导致输入 "02/29" 这类日期无法被正确解析(平年没有 2 月 29 日);
  • 解析结果会被默认到一个“假定的年份”,容易产生隐蔽的逻辑错误。

自 Python 3.15 起,此类调用会抛出 DeprecationWarning,3.17 将直接报错。仓库源码 Lib/_strptime.py 中可以看到警告的具体文案:

Parsing dates involving a day of month without a year specified is ambiguous
and fails to parse leap day. '%e' without a year will become an error in Python 3.17.
To avoid trouble, add a specific year to the input and format.

迁移方式很简单:在格式串和输入字符串中都补上年份。例如:

from datetime import datetime

# 旧写法(3.15+ 触发 DeprecationWarning,3.17 报错)
# datetime.strptime("29", "%e")

# 推荐写法:显式带年份
datetime.strptime("2024-02-29", "%Y-%m-%e")

从源码结构看,该校验发生在 _strptime 对格式串做预处理时,检测到“格式串含 %e 且不含年份”的组合即触发警告,因此迁移时保证格式串与输入成对补全年份即可通过。

collections.abc:ByteString 是“没有方法的 ABC”,请用 Buffer

collections.abc.ByteString 的设计初衷是作为 bytesbytearray 的公共超类(ABC)。但它自诞生起就没有定义任何方法——一个没有抽象方法的 ABC,isinstance 检查通过与否并不能告诉你对象具备什么能力;而且 memoryview 等其他常见缓冲类型也从来不被运行时或静态类型检查器识别为 ByteString 的子类型。官方因此按 PEP 688 的选项将其排期移除。

3.17 移除后的替代方案分两种场景:

  • 运行时类型检查:改用 collections.abc.Buffer,判断对象是否实现了 buffer 协议

    import collections.abc
    
    isinstance(obj, collections.abc.Buffer)  # True 表示 obj 支持 buffer 协议
    
  • 类型标注:使用 collections.abc.Buffer,或显式写出代码实际支持的类型联合,例如 bytes | bytearray | memoryview

源码层面,Lib/_collections_abc.py 已经落实了弃用行为:ByteString 挂了一个 _DeprecateByteStringMeta 元类,重写了 __new____instancecheck__,任何子类化或 isinstance 检查都会通过 warnings._deprecated("collections.abc.ByteString", remove=(3, 17)) 发出警告。类定义本身也被标注为“Deprecated ABC … scheduled for removal in Python 3.17”,并通过 ByteString.register(bytes)ByteString.register(bytearray) 保留旧有的注册关系以维持向后兼容。

encodings:normalize_encoding 不再接受非 ASCII 编码名

encodings.normalize_encoding 负责把编码名规范化:压缩替换掉除 Python 包名用点之外的非字母数字字符(例如 ' -;#' 会变成 _),并去掉首尾下划线。其文档字符串一直强调“编码名应当只含 ASCII 字符”,仓库实现 Lib/encodings/init.py 中现在对违反这一约定的输入直接发出警告:

if not encoding.isascii():
    import warnings
    warnings.warn(
        "Support for non-ascii encoding names will be removed in 3.17",
        DeprecationWarning, stacklevel=2)

3.17 移除后,传入含非 ASCII 字符的编码名将不再被支持。迁移建议:

  • 检查代码中调用 normalize_encoding(或依赖它对编码名做宽松处理)的路径,统一改为传入 ASCII 编码名;
  • 上游若从用户输入或文件头取得编码名,应在调用前做白名单校验,而不是依赖规范化函数的容错。

profile 模块整体弃用:迁移到 profiling.tracing

这是清单中影响面最大的一项:整个 profile 模块将在 3.17 被移除,官方指定替代者是 profiling.tracing 子包,它提供 API 兼容的 tracing profiler(cProfile 同样可用)。依据是 PEP 799(新一代 profiling 标准库包的设计)。

仓库中 Lib/profile.py 在模块导入阶段就发出警告:

warnings.warn(
    "The profile module is deprecated and will be removed in Python 3.17. "
    "Use profiling.tracing (or cProfile) for tracing profilers instead.",
    DeprecationWarning,
    stacklevel=2
)

这意味着 import profile 本身就会触发警告,而不是等用到具体函数时。新的 profiling 包在仓库中位于 Lib/profiling/,包含 tracing(事件追踪型 profiler)与 sampling(采样型 profiler)两个子包。迁移时注意:

  • 命令行 python -m profile 的用法应改为对应的 profiling.tracing 入口或 cProfile
  • 代码中 profile.Profile 的对象式用法,对应到 profiling.tracing 中兼容的 API;
  • 如果只需要统计型性能分析且不想引入新 API,直接换用成熟稳定的 cProfile 是最小改动路径。

webbrowser:macOS 上 MacOSXOSAScript 让位给 MacOS

webbrowser 模块在 macOS 上提供两类浏览器启动器:

  • webbrowser.MacOS:对 http/https URL 直接调用 /usr/bin/open(由 macOS 分发给注册的默认浏览器);其他 URL scheme 或指定浏览器名时,使用 /usr/bin/open -b <bundle-id> 确保 URL 交给浏览器应用而非系统文件处理器;
  • webbrowser.MacOSXOSAScript:旧实现,通过 OSA script 方式启动浏览器,已被前者完全取代。

Lib/webbrowser.py 中可以看到两者现状:MacOS 是功能完整的 BaseBrowser 子类;而 MacOSXOSAScript.__init__ 第一行就是 warnings._deprecated("webbrowser.MacOSXOSAScript", remove=(3, 17))

迁移很简单:凡是代码里直接实例化 MacOSXOSAScript 的地方,替换为 MacOS 即可,行为上后者更贴合 macOS 的 URL 分发机制。

typing:私有类 _UnionGenericAliastyping.ByteString 双双排期移除

typing 模块有两个独立的移除项:

1. typing._UnionGenericAlias(私有实现细节)

Python 3.14 之前,旧式 typing.Union 用私有类 _UnionGenericAlias 实现。3.14 起该私有类不再被实现使用,仅为兼容而保留,3.17 移除。仓库 Lib/typing.py 中保留的“兼容垫片”明确说明其定位:

class _UnionGenericAlias(metaclass=_UnionGenericAliasMeta):
    """Compatibility hack.

    A class named _UnionGenericAlias used to be used to implement
    typing.Union. This class exists to serve as a shim to preserve
    the meaning of some code that used to use _UnionGenericAlias
    directly.
    """

__new____instancecheck____subclasscheck____eq__ 均会发出 warnings._deprecated("_UnionGenericAlias", remove=(3, 17))。文档给出的指引是:不要用私有实现细节做类型检查,改用公开的检查辅助函数:

from typing import get_origin, get_args

get_origin(int | str)          # -> typing.Union
get_args(int | str)            # -> (int, str)

2. typing.ByteString

typing.ByteString 自 3.9 起弃用,3.17 移除。Lib/typing.py 中它已经“懒加载化”:模块顶层不再常驻该名字,__getattr__ 命中 "ByteString" 时才构造一个 _DeprecatedGenericAlias(origin 指向 collections.abc.ByteString),并在访问和 __subclasscheck__ 时分别发出 warnings._deprecated(..., remove=(3, 17)) 警告。

替代方案与上一节 collections.abc.ByteString 完全一致:运行时用 isinstance(obj, collections.abc.Buffer),标注用 Bufferbytes | bytearray | memoryview 之类的显式联合。由于两个 ByteString 同期移除,代码库中应一次性把 typing.ByteStringcollections.abc.ByteString 的引用全部清理。

tkinter:Variable 的旧式 trace 方法让位给 trace_add/trace_remove/trace_info

tkinter.Variable 自 3.14 起弃用了四个方法:trace_variabletrace(前者别名)、trace_vdeletetrace_vinfo,3.17 移除。它们的问题在于:包装的 Tcl 旧式 trace 命令在 Tcl 9.0 中被删除,因此这些方法在 Tcl 9 环境下本来就不受支持。

仓库 Lib/tkinter/init.py 中新旧两套 API 并存:

  • 新 API:trace_add(mode, callback)"read" / "write" / "unset"(或它们的列表/元组)注册回调并返回回调名;trace_remove 用该名字移除;trace_info 查询全部 trace 信息;
  • 旧 API:trace_variabletrace_vdeletetrace_vinfo 各自发出形如 "trace_variable() is deprecated and will be removed in Python 3.17; use trace_add() instead. It is not supported with Tcl 9."DeprecationWarning

迁移示例:

from tkinter import Tk, StringVar

root = Tk()
var = StringVar()

def on_change(mode, index):
    print("variable changed:", var.get())

# 旧写法(3.17 移除,且不支持 Tcl 9)
# var.trace_variable(on_change)
# var.trace_vdelete("read", on_change.__name__)

# 新写法
cbname = var.trace_add("write", on_change)
var.trace_remove("write", cbname)
print(var.trace_info())

注意新 API 的回调签名是 (mode, index) 两参形式,从 trace_variable 迁移时需按 Tcl/Tk 文档核对回调参数。

升级 3.17 前的检查清单

结合仓库源码,可以用下面几条快速扫描存量代码:

  1. 静态搜索:检索 ByteString_UnionGenericAliasnormalize_encodingprofile(import 语句)、MacOSXOSAScripttrace_variable / trace_vdelete / trace_vinfo,逐一替换为上表替代方案;
  2. 格式串审计:搜索 strptime 调用点,确认凡含 %e 的格式串都同时含年份格式符(如 %Y);
  3. 打开告警验证:在现有版本运行 python -W error::DeprecationWarning,仓库中上述每个弃用点都已接入 warnings._deprecated(..., remove=(3, 17)) 或等价的 DeprecationWarning,可以在 3.17 到来之前提前暴露所有触发路径;
  4. profiling 入口:检查 CI、脚本与文档中的 python -m profile 用法,统一改为 profiling.tracingcProfile,避免导入即告警。

这些条目共同体现了 CPython 的弃用节奏:先以 DeprecationWarning 给出明确的移除版本(3.17)与替代 API,源码中同步保留兼容垫片(如 _UnionGenericAliasCompatibility hack),再在目标版本正式移除。按清单完成迁移后,代码即可平滑过渡到 Python 3.17。

登录后查看全文
热门项目推荐
相关项目推荐