CPython 3.14 待移除特性清单:12 项标准库弃用移除的完整解读与迁移指南
CPython 官方通过 Doc/deprecations/ 目录下的清单文件集中记录“将在某个版本中移除”的弃用特性,其中 pending-removal-in-3.14.rst 是 Python 3.14 的 Python 层移除清单,涵盖 argparse、ast、asyncio、itertools、multiprocessing 等 12 个模块的 API 变更。本文逐项解读这份清单中每个待移除特性的含义、替代方案,并结合当前仓库源码验证这些移除在实现层面的最终形态,帮助你在升级 Python 3.14 之前完成兼容性排查与代码迁移。
一、背景:这份“待移除清单”在 CPython 文档体系中的位置
CPython 的弃用(deprecation)遵循一个明确的流程:某个 API 先在文档中标记弃用、随后发出 DeprecationWarning,最后在目标版本中彻底移除。为了跟踪处于“等待移除”阶段的特性,维护者在 Doc/deprecations/ 目录下为每个大版本维护一份清单,例如 pending-removal-in-3.14.rst、c-api-pending-removal-in-3.14.rst(C API 单独成册)以及 pending-removal-in-future.rst。
需要注意适用前提:本清单描述的是 Python 3.14 的移除计划,条目大多是在 3.13 及更早版本中发出弃用警告、并承诺“3.14 移除”的特性。而当前仓库主分支的版本标识为 Include/patchlevel.h 中的 3.16.0a0,即仓库中的源码已经历了 3.14 的移除动作——这使得我们可以直接对照源码,验证“移除之后长什么样”,这是官方文档本身无法提供的纵深信息。
二、argparse:BooleanOptionalAction 的三个参数被移除
清单原文:
argparse:BooleanOptionalAction的type、choices、metavar参数已弃用,将在 3.14 移除(gh-92248,Nikita Sobolev 贡献)。
BooleanOptionalAction 的作用是根据一个选项自动派生其否定形式(--foo 自动得到 --no-foo),它只接受布尔值,因此 type(值转换函数)、choices(取值集合)、metavar(帮助文本占位符)这三个参数在语义上毫无意义——它们从未被真正实现过。
从当前仓库源码可以看到移除的最终形态,Lib/argparse.py 中 BooleanOptionalAction.__init__ 的签名只剩下六个合法参数:
def __init__(self,
option_strings,
dest,
default=None,
required=False,
help=None,
deprecated=False):
其内部逻辑也印证了它只关心“正/负选项对”:对 --foo 形式拼接出 --no-foo,对 -foo 形式拼接出 -nofoo,并在命名冲突时直接抛 ValueError。
迁移方式:检查代码中是否存在如下调用,删除多余的三个关键字参数即可:
# 移除前的写法(3.14 起无效)
parser.add_argument('--verbose',
action=argparse.BooleanOptionalAction,
type=bool, # 删除
choices=[True, False], # 删除
metavar='BOOL') # 删除
# 迁移后的写法
parser.add_argument('--verbose',
action=argparse.BooleanOptionalAction,
default=False,
help='Enable verbose output')
三、ast:五个遗留节点类被移除,统一使用 Constant
清单原文:ast 模块的 ast.Num、ast.Str、ast.Bytes、ast.NameConstant、ast.Ellipsis 五个类自 3.8 起在文档中被弃用,3.14 起访问它们会发出 DeprecationWarning 并被移除,统一改用 ast.Constant(gh-90953,Serhiy Storchaka 贡献)。
这五个类是 Python 3.8 引入统一的 Constant 节点之前的历史遗留:当时每种字面量有自己的 AST 节点(Str 表示字符串、Num 表示数字、NameConstant 表示 True/False/None、Ellipsis 表示 ...、Bytes 表示 b'...')。3.8 之后解析器只产生 ast.Constant,旧类仅保留为兼容别名。
在当前仓库的 Lib/ast.py 中可以直接确认移除结果:文件中已不存在 class Num、class Str 等定义(class Num 等类名在主 AST 定义文件中检索无结果)。
迁移方式:所有对 AST 节点的判断都应改为检查 Constant 及其 value 属性:
import ast
# 旧代码(针对 3.8 之前的解析结果)
if isinstance(node, ast.Str):
print(node.s)
# 新代码(3.14 起的唯一正确写法)
if isinstance(node, ast.Constant) and isinstance(node.value, str):
print(node.value)
注意区分:node.s / node.n 这类旧属性访问同样随旧节点类一起消失,必须使用 node.value。如果你维护的是 AST 转换工具(如 linter、代码生成器),建议用 ast.walk 遍历后用 isinstance(node, ast.Constant) 加类型判断替代所有历史分支。
四、asyncio:Child Watcher 体系整体移除
清单原文(gh-94597,Kumar Aditya 贡献)分两部分:
asyncio.MultiLoopChildWatcher、asyncio.FastChildWatcher、asyncio.AbstractChildWatcher、asyncio.SafeChildWatcher四个子类观察者类已弃用,3.14 移除;asyncio.set_child_watcher、asyncio.get_child_watcher、asyncio.AbstractEventLoopPolicy.set_child_watcher、asyncio.AbstractEventLoopPolicy.get_child_watcher四个函数/方法一并移除。
Child Watcher 是 asyncio 监控子进程退出的旧机制(基于 SIGCHLD 信号或线程),它在多线程环境下容易与用户代码抢占信号处理,长期是 asyncio 的痛点。维护者内部早已用基于 pidfd(Linux 5.3+)和独立线程的私有实现替代了公开 API。
当前仓库 Lib/asyncio/unix_events.py 印证了移除后的状态:公开 API 中已无 ChildWatcher 家族,只剩两个私有类——_PidfdChildWatcher(L891)与 _ThreadedChildWatcher(L927),事件循环构造时按平台能力自动选择(优先 pidfd,不可用时回退线程方案)。从源码结构看,用户代码无需也不应再手动配置子进程监控器,它已成为 asyncio 的内部实现细节。
迁移方式:删除所有 asyncio.get_child_watcher() / asyncio.set_child_watcher(...) 调用及 SafeChildWatcher 等类的 isinstance 检查。对于“在多线程服务中安全运行子进程”这一原始诉求,直接用 loop.subprocess_exec() / loop.subprocess_shell() 即可,asyncio 内部会选择合适的 watcher。
get_event_loop 的弃用警告语义变化
清单同章节还有一条(gh-100160,Serhiy Storchaka 与 Guido van Rossum 贡献):默认事件循环策略的 asyncio.get_event_loop() 在没有当前事件循环且它决定要新建一个时,会发出 DeprecationWarning。
这条不是“移除”,而是警告行为收紧:在协程内运行代码、或用 asyncio.run() 的场景不应手动调 get_event_loop()。迁移后的规范写法是:
# 异步代码内部:获取“正在运行”的循环
loop = asyncio.get_running_loop()
# 同步代码入口:直接托管给 asyncio.run
asyncio.run(main())
五、email:localtime 的 isdst 参数移除
清单原文:email.utils.localtime 的 isdst 参数已弃用(gh-72346,Alan Williams 贡献)。
该参数原本用于在“夏令时状态未知”的模糊时刻强制指定按夏/冬令时解释时间,但这一语义在 astimezone 转换路径中并不可靠。当前仓库 Lib/email/utils.py 中签名已简化为:
def localtime(dt=None):
"""Return local time as an aware datetime object.
If called without arguments, return current time. Otherwise *dt*
argument should be a datetime instance, and it is converted to the
local time zone according to the system time zone database. If *dt*
is naive (that is, dt.tzinfo is None), it is assumed to be in local time.
"""
if dt is None:
dt = datetime.datetime.now()
return dt.astimezone()
迁移方式:调用时直接不传 isdst;需要精确时区语义时,改用带 zoneinfo.ZoneInfo 时区的 datetime 显式转换。
六、importlib.abc:资源读取协议类迁移到 importlib.resources.abc
清单原文(gh-93963,Jason R. Coombs 与 Hugo van Kemenade 贡献):importlib.abc.ResourceReader、importlib.abc.Traversable、importlib.abc.TraversableResources 已弃用,改用 importlib.resources.abc.Traversable 与 importlib.resources.abc.TraversableResources。
这轮迁移的背景是 PEP 645(资源 Traversable 协议)落地后,资源协议类被安置在语义更准确的 importlib.resources.abc 子模块中,importlib.abc 只保留与模块导入机制(PEP 302)直接相关的 ABC。
当前仓库中两侧的现状都可以验证:
- 新位置 Lib/importlib/resources/abc.py 定义着
class Traversable(Protocol)(L71)与class TraversableResources(ResourceReader)(L169); - 旧位置 Lib/importlib/abc.py 的
__all__与类列表中已无这三个名字,仅保留MetaPathFinder、PathEntryFinder、ResourceLoader(L86)等导入机制 ABC。
迁移方式:修改 import 语句即可:
# 旧
from importlib.abc import Traversable, TraversableResources, ResourceReader
# 新
from importlib.resources.abc import Traversable, TraversableResources
# ResourceReader 若仍需鸭子类型检查,可参考 importlib.resources 提供的辅助函数
如果你的包通过 importlib.resources.files() 暴露资源,通常无需改动,只有做 isinstance 协议检查或自定义 ABC 实现时才受影响。
七、itertools:copy / deepcopy / pickle 支持被移除
清单原文(gh-101588,Raymond Hettinger 贡献):itertools 对 copy、deepcopy、pickle 的支持“没有文档记载、效率低下、历史上 bug 频发且行为不一致”,将在 3.14 移除,以显著减少代码量与维护负担。
这条移除的实际影响面比看上去大:此前 itertools 的迭代器 C 实现普遍实现了 tp_copy、tp_reduce 等槽,导致 copy.copy(it)、pickle.dumps(it) 在部分迭代器类型上“碰巧能工作”,但语义不可靠。移除后统一行为是:迭代器对象不可复制、不可 pickle(抛出 TypeError)。
当前仓库 Modules/itertoolsmodule.c 反映了精简后的状态:全文件中 __copy__ 仅保留在一处——tee 迭代器的 tee_copy(L1121,返回“独立迭代器”tee_copy_impl),其余迭代器类型不再携带复制/序列化槽。
迁移方式:如果代码依赖“pickle 一个进行到一半的迭代器”来断点续算,必须改为显式设计:
# 反模式:pickle 迭代器中间状态(3.14 起不可用)
data = itertools.cycle(range(100))
next(data)
state = pickle.dumps(data) # TypeError
# 正确做法:自行维护可序列化状态
class ResumableCycler:
def __init__(self, source):
self.source = list(source)
self.index = 0
def __iter__(self):
while True:
yield self.source[self.index % len(self.source)]
self.index += 1
依赖 copy.deepcopy 复制迭代器链(例如把 itertools.tee 之外的迭代器 deepcopy 给多个消费者)的代码,应改为在源头重新构造迭代器,或改用 tee 显式分叉。
八、multiprocessing:默认启动方式改为更安全的选择
清单原文(gh-84559):Linux、BSD 及其他非 macOS POSIX 平台(当前默认 'fork')的默认启动方式将改为更安全的方式;由于大多数代码不关心具体方式,运行时警告被认为过于扰人而不加。若你的代码必须使用 'fork',应通过 multiprocessing.get_context 或 multiprocessing.set_start_method 显式指定。
fork 在多线程进程中不安全(子进程只继承发起 fork 的线程,锁状态、已分配内存都可能不一致),这正是变更动机。该条目值得特别强调的实操要点是:“需要 fork”从默认变成了必须声明。
当前仓库 Lib/multiprocessing/context.py 中可以看到 3.14 起生效的最终实现:
# bpo-33725: running arbitrary code after fork() is no longer reliable
# on macOS since macOS 10.14 (Mojave). Use spawn by default instead.
# gh-84559: We changed everyones default to a thread safeish one in 3.14.
if (
reduction.HAVE_SEND_HANDLE
and sys.platform != 'darwin'
# gh-155717: forkserver requires to write temporary files
and util._has_writeable_tempdir()
):
_default_context = DefaultContext(_concrete_contexts['forkserver'])
else:
_default_context = DefaultContext(_concrete_contexts['spawn'])
即:在支持 forkserver(需要 HAVE_SEND_HANDLE,即 sendfile 句柄传递)且临时目录可写的非 Darwin 平台,默认启动方式为 forkserver;否则回退到 spawn。fork 仍然可用,只是不再默认。
迁移方式:
import multiprocessing
# 显式声明“我就是要 fork”(两条等价路径)
ctx = multiprocessing.get_context('fork')
ctx.Process(target=worker, args=(payload,)).start()
# 或在入口一次性设定进程内默认方式
multiprocessing.set_start_method('fork', force=True)
排查方法:全局搜索直接 import multiprocessing 后使用 multiprocessing.Process / Pool / Queue 的代码路径——这些隐式走默认上下文;若进程内运行着线程池、I/O 多路复用或第三方库后台线程,切到 forkserver/spawn 后需确认 if __name__ == '__main__' 保护齐全、可被 pickle 的对象满足 spawn 的序列化要求。
九、pathlib:is_relative_to 与 relative_to 禁止多参数
清单原文:pathlib.PurePath.is_relative_to 与 PurePath.relative_to 传入额外参数(多个 other)已弃用。
这两个方法自诞生起就接受可变的多个 *other 路径,但多参数用法(等价于逐个调用)从未被广泛依赖。当前仓库 Lib/pathlib/types.py 中两者的签名已收敛为单参数:
def relative_to(self, other, *, walk_up=False): # L237
def is_relative_to(self, other): # L259
迁移方式:多个基准路径改为循环判断:
p = Path('/a/b/c/file.txt')
# 旧:一次传多个基准(3.14 起弃用/移除)
# p.is_relative_to(Path('/a'), Path('/x/y'))
# 新:逐个判断
any(p.is_relative_to(base) for base in (Path('/a'), Path('/x/y')))
十、pkgutil:find_loader 与 get_loader 发出 DeprecationWarning
清单原文(gh-97850,Nikita Sobolev 贡献):pkgutil.find_loader 与 pkgutil.get_loader 现在会发出 DeprecationWarning,应改用 importlib.util.find_spec。
这两个函数是 PEP 302 早期设计的“绕过 import 直接找 loader”的旧通道,长期与导入机制的演化(path hooks、finders 链)脱节,行为在复杂导入场景下不可靠。当前仓库 Lib/pkgutil.py 中已无这两个公开函数(文件内仅在内部实现注释/调用处残留 find_loader 字样),移除已完成。
迁移方式:
# 旧
import pkgutil
loader = pkgutil.get_loader('some.module')
found = pkgutil.find_loader('some.module') is not None
# 新
import importlib.util
spec = importlib.util.find_spec('some.module')
found = spec is not None
注意语义差异:find_spec 找不到模块时返回 None(或抛 ModuleNotFoundError,取决于调用方式),而旧 find_loader 失败时返回 None 但不抛异常——迁移时用 try/except ModuleNotFoundError 或先检查包是否存在来对齐行为。
十一、pty:master_open 与 slave_open 改用 openpty
清单原文:pty.master_open() 请用 pty.openpty 替代;pty.slave_open() 同样用 pty.openpty 替代。
master_open / slave_open 是 openpty 的“拆半”版本,各自只返回一端 fd,历史上还带有“master 端不设置 O_NONBLOCK”这类与 openpty 不一致的行为差异。当前仓库 Lib/pty.py 的公开函数列表中已无这两个函数(grep "^def" 仅剩 openpty、fork、spawn、amaster/aslave 包装等),移除已完成。
迁移方式:
import pty, os
# 旧
master, slave = pty.master_open(), pty.slave_open()
os.close(slave)
# 新(一次拿两端,master 端默认非阻塞)
master, slave = pty.openpty()
注意 openpty 返回的 master 端默认带 O_NONBLOCK 标志,若旧代码依赖阻塞式 master_open() 行为,需要手动 os.clear 该标志或在读取循环中处理 EAGAIN。
十二、sqlite3:version/version_info 与命名占位符的序列参数
清单原文包含两条 sqlite3 变更:
- 模块级数据
sqlite3.version与sqlite3.version_info移除; Cursor.execute/Cursor.executemany在使用命名占位符(:name)而parameters传的是序列(而非dict)时,该用法弃用并将在 3.14 移除。
第 1 条的动机是消除与 C 层 sqlite3.version / Python 层包装版本混淆的双套版本常量。当前仓库 Lib/sqlite3/dbapi2.py 中只保留了 SQLite 库版本相关的 sqlite_version_info(由 sqlite_version 字符串解析而来),模块级 version / version_info 已不存在。迁移方式是改用 sqlite3.sqlite_version(SQLite 库版本);若需要的是 Python 本身版本,使用 sys.version。
第 2 条针对的是“用元组喂给命名占位符”这种隐式按位置匹配的模糊用法,要求显式传字典:
# 旧(命名占位符 + 序列,3.14 起移除)
cur.execute("SELECT * FROM users WHERE id = :uid AND name = :name",
(42, "alice"))
# 新(命名占位符必须配 dict)
cur.execute("SELECT * FROM users WHERE id = :uid AND name = :name",
{"uid": 42, "name": "alice"})
顺带说明:若坚持用序列传参,请将 SQL 改写成位置占位符(? 或 qmark 风格),那是与序列参数明确配套的形式。相关文档参见 Doc/library/sqlite3.rst 的 placeholder 一节。
十三、urllib.parse:Quoter 移除
清单原文(gh-88168,Gregory P. Smith 贡献):urllib.parse.Quoter 已弃用,因为它“本来就不是设计成公开 API”。
Quoter 是 URL 编码实现内部的字典缓存辅助类,早期版本直接暴露,后来一直作为半私有存在。当前仓库 Lib/urllib/parse.py 中该定义已更名为下划线前缀的 _Quoter,公开 API 中不再出现 Quoter;对外仍由 urllib.parse.quote / unquote 等函数承担编码职责(urlsplit、urlencode 等公开函数在模块中均不暴露 Quoter)。
迁移方式:如果你的代码直接构造了 Quoter 实例做字符串转义,请替换为对应的公开函数:
# 旧
from urllib.parse import Quoter
q = Quoter(safe='/:?[]@!$&\'()*+,;=')
encoded = q(value)
# 新
from urllib.parse import quote
encoded = quote(value, safe="/:?[]@!$&'()*+,;=")
十四、系统性排查:如何在代码库中找出所有受影响的调用
以上 12 个条目分属不同模块,建议在升级前做一轮自动化扫描,而不是依赖人工记忆:
-
静态扫描关键符号,对以下名字在代码库中全文检索(含字符串形式的反射调用):
ast.Num、ast.Str、ast.Bytes、ast.NameConstant、ast.Ellipsis、get_child_watcher、set_child_watcher、SafeChildWatcher、FastChildWatcher、MultiLoopChildWatcher、importlib.abc.ResourceReader、importlib.abc.Traversable、pkgutil.find_loader、pkgutil.get_loader、pty.master_open、pty.slave_open、sqlite3.version、sqlite3.version_info、urllib.parse.Quoter; -
运行时暴露告警:用严格模式跑完整测试套件,把弃用警告变成错误:
python -W error::DeprecationWarning -X dev -m unittest discover -s test-X dev会额外开启开发模式检查(未触发警告的弃用行为、资源泄漏提示等),-W error::DeprecationWarning让清单中“发出警告”类的条目(如get_event_loop建环警告、sqlite3命名占位符误用)在第一次触发时即失败; -
关注行为变更类条目:
itertools的 copy/deepcopy/pickle 移除和multiprocessing默认启动方式变更不产生告警(文档中明确说明不加运行时警告),只能靠上述第 1 步的代码审查和回归测试覆盖。
十五、小结
Doc/deprecations/pending-removal-in-3.14.rst 所记录的 3.14 移除清单整体呈现三条主线:
- API 收敛:
ast五节点归一为Constant、importlib.abc资源协议迁入importlib.resources.abc、pty两端 open 函数归一到openpty、pkgutilloader 查询归一到importlib.util.find_spec——旧入口被新入口完全取代,迁移通常只是改名与改 import; - 行为安全化:
multiprocessing默认启动方式离开fork、asyncio.get_event_loop()收紧警告——目标是把“碰巧能用”的默认行为变成显式声明; - 实现细节私有化:
itertools的复制/序列化支持、urllib.parse.Quoter、sqlite3模块级版本常量——从未承诺过稳定的内部实现被彻底收回。
结合本仓库源码可以确认,这些移除在 3.16 主分支中均已落地(如 Lib/argparse.py 的简化签名、Lib/multiprocessing/context.py 的 forkserver 默认分支、Lib/urllib/parse.py 的 _Quoter 私有化)。对照本文各节的“迁移方式”逐项处理后,代码即可平滑运行于 Python 3.14 及之后的版本。
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 StartedRust0626
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