首页
/ CPython 3.5.0b3 技术里程碑全解析:PEP 492 原生协程、RecursionError 与内存安全修复

CPython 3.5.0b3 技术里程碑全解析:PEP 492 原生协程、RecursionError 与内存安全修复

2026-09-09 11:04:41作者:晏闻田Solitary

本文基于 CPython 仓库中的版本说明文档 Misc/NEWS.d/3.5.0b3.rst,系统梳理 Python 3.5.0 第三个测试版(3.5.0b3,2015-07-05 发布)带来的语言核心、标准库与构建链变更。读完本文,你将完整掌握 PEP 492 原生协程的引入方式、RecursionError 的设计动机、bytearray 缓冲区安全修复的底层原理,以及标准库中一批高价值缺陷修复的来龙去脉,并了解这些变更在当前 CPython 源码树中的对应实现位置。

一、版本背景:3.5.0b3 在发布周期中的位置

3.5.0b3 是 CPython 3.5 正式版(2015-09-13 发布)之前的第三个测试版,属于功能冻结(feature freeze)之后的缺陷修复与收尾阶段。从版本说明条目分布看,b3 的变更集中在三个方面:

  1. 语言核心(Core and Builtins):完成 PEP 492 协程的类型系统落地、新增 RecursionError 异常、升级 Unicode 8.0.0、加固 bytearray 缓冲区。
  2. 标准库(Library):围绕 mock_openOrderedDicttarfileElementTreetokenizeaudioopcontextmanagerjsoncmathtkinterlru_cache_pickle 的一批真实缺陷修复。
  3. 构建(Build)与文档(Documentation):Windows 与 OS X 10.5 安装包升级 OpenSSL 1.0.2c,PEP 489 多阶段初始化文档落地。

从源码树中的 Include/internal/pycore_magic_number.h 可以看到,3.5b3 对应字节码 magic number 3350,其注释明确记录"add GET_YIELD_FROM_ITER opcode #24400",这正是 PEP 492 协程支持在字节码层面的印记。

二、语言核心(Core and Builtins)变更详解

2.1 PEP 492:原生协程类型的完整引入(bpo-24400)

这是 3.5.0b3 中分量最重的变更。此前协程基于生成器(generator)实现,通过 collections.abc.Coroutine 等抽象基类进行鸭子类型判定。b3 起,协程拥有了独立的运行时类型,具体措施包括:

  • 新增 types.CoroutineType,作为 async def 函数的返回值类型;在 Lib/types.py 中可以看到其构造方式:async def _c(): pass; _c = _c(); CoroutineType = type(_c)
  • 新增 inspect.getcoroutinestate()inspect.getcoroutinelocals() 内省函数;当前实现位于 Lib/inspect.pygetcoroutinestate 返回 cr_state,可能的取值为 CORO_CREATED(等待启动)、CORO_RUNNING(执行中)、CORO_SUSPENDED(在 await 处挂起)、CORO_CLOSED(已完成);getcoroutinelocals 则返回协程帧 cr_frame.f_locals 的局部变量映射;
  • 协程对象不再使用 CO_GENERATOR 标志,与生成器在字节码与对象模型上彻底分离;
  • sys.set_coroutine_wrapper() 仅对纯 async def 协程生效;
  • inspect.iscoroutine() 不再依赖 collections.abc.Coroutine,只用于判定"纯 async def 协程";
  • 新增字节码 GET_YIELD_FROM_ITER,用于在 yield from / await 链路中更精确地区分迭代器与可等待对象(awaitable);
  • types.coroutine() 包装生成器时,产生的包装对象必须是 collections.abc.Generator 的实例;
  • collections.abc.Awaitablecollections.abc.Coroutine 不再用于检测基于生成器的协程,统一改用 inspect.isawaitable()

对普通开发者而言,这段变更意味着 type(async_func()) 不再是 generator,而是全新的 coroutine 类型;异步代码的调试与内省(状态、局部变量)从此有了稳定的语言级入口。

2.2 新增 gi_yieldfromcr_await 属性(bpo-24450)

生成器新增 gi_yieldfrom 属性,返回 yield from 当前委托的子迭代器(若无则返回 None);协程新增对应的 cr_await 属性,返回当前 await 的可等待对象。该变更由 Benno Leslie 与 Yury Selivanov 贡献,配合 GET_YIELD_FROM_ITER 字节码,让调试器、asyncio 及各类诊断工具能够在运行时精确观测协程的挂起点。

2.3 新增 RecursionError 异常(bpo-19235)

递归深度超限从此不再抛出笼统的 RuntimeError,而是专门的 RecursionErrorRuntimeError 的子类)。在当前源码中,Objects/exceptions.c 通过 SimpleExtendsException(PyExc_RuntimeError, RecursionError, ...) 定义其继承关系,异常表(Objects/exceptions.c 第 4534 行附近的 ITEM(RecursionError) 条目)中也已注册该类型。

这个变更的工程意义在于:递归深度溢出是"可预期、可恢复"的编程错误,而 RuntimeError 承载了大量语义不同的运行时错误。有了独立异常类型后,诸如深度优先搜索改写为迭代、递归转义守卫、调试器栈深度分析等场景都能精确捕获而不误伤其他 RuntimeError

2.4 bytearray 缓冲区安全修复(bpo-24467)

bytearray 对象现在始终为末尾空字节(trailing null byte)分配空间,且其缓冲区恒为 null 结尾。这修复了可能的缓冲区过度读取(buffer over-read)。

从缓冲区安全角度看,此前某些路径会读取超过 len 的字节(例如以 C 字符串方式访问 bytearray 内部缓冲的 C 扩展代码),导致越界读。强制 null 结尾后,即便扩展模块误将其当作 C 字符串使用,也不会越界访问未映射内存。这是典型的"用分配冗余换内存安全"的设计取舍,与 bytesstr 一贯的 null 结尾策略保持一致。

2.5 Unicode 升级至 8.0.0

b3 将内置 Unicode 数据升级到 Unicode 8.0.0(2015 年 6 月发布的标准版本),影响 str 的规范化、大小写转换、unicodedata 模块数据以及标识符合法性判定。该变更对使用新版本字符(新增脚本、扩展区汉字、emoji 增补等)的应用直接生效。

2.6 稳定 ABI 新增 Py_tp_finalize 槽位(bpo-24345)

为稳定 ABI(Stable ABI)新增 Py_tp_finalize 槽位。tp_finalize 是对象终结回调,在对象被 GC 回收前执行(包括循环引用场景)。将其纳入稳定 ABI,意味着使用稳定 ABI 编译的第三方扩展无需随解释器小版本升级而重新编译,即可使用终结逻辑。

三、标准库(Library)关键修复逐一剖析

3.1 mock_open.read_data 回归修复(bpo-21750)

unittest.mockmock_open 恢复了 Python 3.3 时代的语义:read_data 可以被每个 mock 实例独立读取。当前实现位于 Lib/unittest/mock.pymock_open(mock=None, read_data='') 内部通过 _to_streamread_data 包装为 io.StringIOio.BytesIO 流,并把流对象存入 _state 列表,随后 read/readline/readlines/__iter__ 均从该流取值。恢复"按实例读取"后,多个 mock 句柄可以各自消费自己的数据流,互不干扰,这对模拟"多次打开同一文件、每次读到不同内容"的测试场景至关重要。

3.2 _pickle 错误路径 use-after-free 修复(bpo-24552)

C 实现的 pickle 模块 _pickle 修复了一个错误处理分支中的释放后使用(use after free)问题。这类缺陷通常出现在"先释放临时对象、后读取其引用"的错误恢复路径上,极易在畸形 pickle 数据或内存紧张时触发崩溃或内存破坏。该修复与 3.5.0b3 中多项内存安全修复(bytearray、audioop、json 溢出)同属"对 C 实现的系统化安全加固"。

3.3 tarfile 容忍纯空白数字字段(bpo-24514)

tarfile 现在允许数字字段(如 size、mtime)仅包含空白字符。旧实现会直接抛异常,导致解压来自部分非标准 tar 生成器的归档失败;新行为将其视为可容错输入,提升了与第三方产物的互操作性。

3.4 ElementTree doctype() 行为修正(bpo-19176)

C 实现的 xml.etree.ElementTree 修复了多个 doctype() 相关缺陷:

  • 使用默认 doctype() 方法的 XMLParser 子类不再被错误地发出弃用警告;
  • 直接调用 doctype() 现在会发出警告;
  • 若 target 的 doctype() 被调用,则不再重复调用 Parser 的 doctype()

这消除了重复通知与误导性警告,使自定义 XML 解析器能够可靠地处理文档类型声明。

3.5 tokenize/untokenize 制表符缩进往返一致性(bpo-20387)

修复了 tokenize/untokenize制表符缩进代码块上的语义往返(round-trip)正确性。此前对以 tab 缩进的代码做"分词再重组",可能产生与原文不等价的输出。该修复保证了 untokenize(tokenize(code)) 在缩进语义上与原始源码一致,是依赖 tokenize 做源码转换、格式化工具(如代码重排脚本)正确性的基础。

3.6 audioop 编解码越界读修复(bpo-24456)

audioop 模块的 adpcm2lin()lin2adpcm() 修复了可能的缓冲区过度读取。该模块用 C 实现 ADPCM 音频编解码,按帧处理采样数据,边界索引计算错误会引入越界读。需要说明的是:audioop 模块在此后版本中已从标准库移除(其功能由第三方库取代),因此该修复仅针对 3.5 时代的代码路径,当前 CPython 主分支源码树中已无 Modules/audioop.c 这一实现文件。

3.7 contextmanager 支持名为 func/self 的关键字参数(bpo-24336)

contextlib.contextmanager 装饰器此前无法正确处理关键字参数名为 funcself 的被装饰函数,因为内部实现将这两个名字用作变量。修复后,这样的函数可以正常使用。从当前 Lib/contextlib.py_GeneratorContextManagerBase/_GeneratorContextManager 实现看,装饰器会将 funcargskwds 原样保存,并在 _recreate_cm() 中按需重建上下文管理器实例——这正说明内部命名空间必须与被装饰函数解耦,才能支持任意形参名的函数。

3.8 json 加速模块整数溢出修复(bpo-24522)

C 实现的 _json 加速模块修复了可能的整数溢出问题。JSON 解析中的长度/索引运算(如字符串转义展开后的缓冲增长计算)若使用不当宽度的整数类型,超大输入可导致溢出并破坏缓冲区,这是 C 实现的典型攻击面。修复后,畸形超大 JSON 输入会安全报错而非产生内存破坏。

3.9 cmath.polar() 不受残留 C errno 干扰(bpo-24489)

cmath.polar() 现在会先确保之前设置的 C errno 不会干扰其结果。复数极坐标转换在内部依赖 C 数学库,而 errno 是线程级的全局状态:若上层代码在此之前恰好触发了 errno = EDOM 等错误,旧实现可能误报异常。该修复保证了 polar() 的错误判定只依据本次调用本身。

3.10 tkinter.Font.measure()metrics() 修复(bpo-24408)

tkinter.Fontmeasure()metrics() 方法修复了 AttributeError。这两个方法返回字体尺寸/度量信息,此前在特定 Tcl/Tk 版本或字体配置下会因内部字符串转换失败而抛 AttributeError,修复后恢复正常返回。

3.11 C 版 functools.lru_cache() 支持方法(bpo-14373)

C 实现的 lru_cache()_functools 模块)现在可以与实例方法一起使用。此前 C 版包装器在"方法作为被缓存函数"的场景下存在缺陷(通常表现为缓存键计算错误或 self 混入缓存),Python 版虽可用但性能较差。修复后,@lru_cache 装饰类方法可直接享受 C 版的高性能。当前实现位于 Modules/_functoolsmodule.c,其核心是 lru_cache_object 结构体(第 1217 行附近)与对应的包装函数族 lru_cache_ternaryfunc——该结构设计正是为了在"绑定方法"与"普通函数"两种调用形态间正确分发。

3.12 字典相关修复(bpo-24347 / 24348)

  • bpo-24347PyDict_GetItemWithError 返回 NULL 时设置 KeyError。该 API 的语义是"查不到键返回 NULL 且不设置异常",调用方需自行区分"键不存在"与"真实错误"。此前的某些调用点未正确处理这两种情况,导致错误路径下异常状态缺失。修复后相关内部代码在 NULL 返回时正确抛出 KeyError。当前 Objects/dictobject.c 附近仍保留 PyDict_GetItemWithError 的实现,并在第 2474 行附近的错误信息中明确提示调用方改用 PyDict_GetItemRef() 或正确处理其返回语义。
  • bpo-24348:删除多余的 incref/decref 引用计数操作,消除不必要的开销与潜在的双重释放窗口。

四、OrderedDict 系列加固(bpo-24359 / 24368 / 24362 / 24377 / 24369)

3.5.0b3 对 C 实现的 OrderedDict_collections 模块)做了一轮系统化加固,共 5 项变更:

bpo 编号 变更内容 意义
bpo-24359 迭代期间检查 OrderedDict 是否被修改 防止迭代中结构变更导致的未定义行为
bpo-24368 OrderedDict 方法支持关键字参数 dict 的方法签名对齐,od.update(a=1) 等写法可用
bpo-24362 简化 C 实现 fast nodes 的扩容逻辑 提升双向链表节点数组扩容的清晰度与稳健性
bpo-24377 修复 OrderedDict.__repr__ 的引用泄漏(ref leak) 消除长生命周期 repr 场景下的内存增长
bpo-24369 防御迭代期间键被修改 保证键对象在哈希/比较期间不被意外改写

这五项共同将 C 版 OrderedDict 的"迭代安全 + 内存正确 + API 对齐"补完整,其中"迭代期间修改检查"与 dictRuntimeError: dictionary changed size during iteration 语义保持一致。

五、测试与文档变更

5.1 扩展模块测试的引用泄漏修复(bpo-24373)

测试模块 _testmultiphasexxlimited 改用 tp_traversetp_finalize,以避免"tp_deallocPyType_FromSpec 组合使用"时的引用泄漏(详见历史 issue #16690)。这两个模块正是 PyType_FromSpec API 的官方参考实现,Modules/_testmultiphase.cModules/xxlimited.c 至今仍是扩展模块作者学习"规范的对象销毁/GC 协议"的首选范本。

5.2 文档更新(bpo-24458 / 24351)

  • bpo-24458:更新文档以覆盖 PEP 489 的多阶段初始化(multi-phase initialization),为扩展模块作者提供了 Py_mod_createPy_mod_exec 等新阶段钩子的权威使用说明。
  • bpo-24351:澄清 string.Template 上下文中"identifier"的确切含义,明确了模板变量名的合法字符集合,减少误用。

六、构建系统:OpenSSL 升级(bpo-24432)

Windows 构建与 OS X 10.5 安装包同步升级到 OpenSSL 1.0.2c。该版本修复了当时已知的若干安全漏洞(包括影响广泛的"POODLE" TLS 变体等),使 sslhashlib 等依赖 OpenSSL 的模块在 3.5.0b3 发布时处于更安全的密码学基线之上。

七、版本说明文件格式说明:如何阅读 Misc/NEWS.d/*.rst

本次分析的 Misc/NEWS.d/3.5.0b3.rst 属于 CPython 的"版本说明补丁"体系。每个条目由固定字段构成:

  • .. bpo: <编号>:关联的 bpo 问题追踪编号(bpo 即 bugs.python.org);
  • .. date: <序号>:条目在版本内的排序序号(数值越大越早);
  • .. nonce: <随机串>:条目的唯一随机标识,防止 blurb 工具合并时冲突;
  • .. release date::本版本的实际发布日期(本文件为 2015-07-05);
  • .. section::变更所属分类(Core and Builtins / Library / Tests / Documentation / Build 等);
  • 正文:一段面向用户的行为描述,末尾常见 Patch by <作者> 署名。

Misc/NEWS.d 目录下共 900+ 个此类 rst 文件,它们会在每次正式发布时由 blurb 工具合并进 Misc/HISTORY 等累积变更日志。对开发者而言,这套文件是"按版本、按分类检索 CPython 历史行为变更"的一手资料:当你在升级解释器后遇到行为差异时,先在对应版本的 NEWS.d 文件中检索 section 与关键词,往往能最快定位到引入变更的 bpo 编号与动机。

八、总结:b3 的工程价值与启示

3.5.0b3 虽然只是 3.5.0 的第三个测试版,却浓缩了 CPython 在"新语言特性落地"与"C 实现安全加固"两条主线的典型节奏:

  1. 特性先行、类型收尾:PEP 492 的语法与字节码在前序版本中铺垫,b3 完成 CoroutineType、内省函数、GET_YIELD_FROM_ITER 等类型系统层面的收尾,使协程成为一等公民;
  2. 专项安全加固:bytearray null 结尾、audioop 越界读、_pickle use-after-free、json 整数溢出,覆盖了缓冲区、生命周期、算术三类 C 语言高危面;
  3. 标准库行为校准mock_opentokenizecontextmanagerOrderedDict 等修复,本质是对"文档承诺行为"与"实际实现行为"的逐一对齐。

理解这些变更,不仅能帮助你在升级到 3.5+(尤其是使用 asyncio、调试协程、编写 C 扩展)时准确预判行为差异,也能让你在阅读当前 CPython 源码(如 Objects/exceptions.cLib/inspect.pyLib/unittest/mock.py)时,快速理解那些"看似历史遗留"的设计细节背后的真实动机。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
924
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
599
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
394