PEP 803 – 自由线程构建的稳定 ABI
- 作者:
- Petr Viktorin <encukou at gmail.com>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 要求:
- 703, 793, 697
- 创建日期:
- 2025年8月19日
- Python 版本:
- 3.15
- 发布历史:
- 2025年9月8日
摘要
稳定 ABI 的 3.15 版本将与自由线程和启用 GIL 的构建兼容。为实现此目的,PyObject
内部结构和相关 API 将从 Limited API 的 3.15 版本中移除,需要迁移到新的 API 来完成定义模块和大多数类的常见任务。
使用 Limited API 3.15 及更高版本构建的二进制分发 (wheel) 应使用 ABI 标签 abi3.abi3t
。
术语
本 PEP 使用“启用 GIL 的构建”作为“自由线程构建”的反义词,即不带 Py_GIL_DISABLED
构建的解释器或扩展。
动机
稳定 ABI 目前不适用于自由线程构建。当定义 Py_LIMITED_API
时,扩展将无法构建,并且为 CPython 的启用 GIL 构建的扩展将在自由线程构建上无法加载(或崩溃)。
在 PEP 779 的接受帖中,指导委员会表示“期望为 Python 3.15 准备和定义自由线程的稳定 ABI”。
本 PEP 提出了自由线程的稳定 ABI。
背景
Python 的稳定 ABI,如 PEP 384 和 PEP 652 中所定义,提供了一种编译扩展模块的方法,这些模块可以在 CPython 解释器的多个次要版本上加载。一些项目使用它来限制每个版本需要构建和分发的 wheels(二进制工件)的数量,和/或使其更容易使用 Python 的预发布版本进行测试。
随着自由线程构建 (PEP 703) 有望最终成为默认 (PEP 779),我们需要一种方法使稳定 ABI 适用于这些构建。
为了针对稳定 ABI 进行构建,扩展必须使用一个 Limited API,即 CPython 暴露的函数、结构等的一个子集。Limited API 是有版本的,针对 Limited API 3.X 构建会产生一个与 CPython 3.X 和 任何 更高版本 ABI 兼容的扩展(尽管 CPython 中的错误有时会在实践中导致不兼容)。此外,Limited API 并非“稳定”:更新的版本可能会删除旧版本中的 API。
本 PEP 提出了迄今为止最重要的此类删除。
基本原理
本 PEP 中的设计有几个假设
- 一个 ABI
- 一个编译的扩展模块应同时支持自由线程和启用 GIL 的构建。
- 目前不向后兼容
- 新的 Limited API 不支持 CPython 3.14 及以下版本。需要此支持的项目可以专门为 3.14 自由线程解释器和旧版稳定 ABI 构建单独的扩展。
但是,我们不会阻止将兼容性扩展到 CPython 3.14 及以下版本的可能性。
- API 更改是可以接受的
- 新的 Limited API 可能要求扩展作者对其代码进行重大更改。暂时无法这样做的项目可以继续使用 Limited API 3.14,这将产生仅与启用 GIL 的构建兼容的扩展。
- 无需额外配置
- 我们不引入影响可用 API 和 ABI 兼容性的新“旋钮”。
规范
不透明 PyObject
Limited API 3.15 将
- 使以下结构 不透明(或在 C 术语中,不完整类型)
PyObject
PyVarObject
PyModuleDef_Base
PyModuleDef
- 不再包含以下宏
PyObject_HEAD
_PyObject_EXTRA_INIT
PyObject_HEAD_INIT
PyObject_VAR_HEAD
Py_SET_TYPE()
- 将以下内容作为 ABI 中的函数导出,而不是宏
影响
使 PyObject
、PyVarObject
和 PyModuleDef
结构不透明意味着
- 它们的字段不能直接访问。
例如,扩展必须使用
Py_TYPE(o)
而不是o->ob_type
。这种用法已有一段时间是首选做法。 - 它们的尺寸和对齐方式将不可用。诸如
sizeof(PyObject)
之类的表达式将不再起作用。 - 它们不能嵌入到其他结构中。这主要影响扩展定义类型的实例结构,这些结构需要使用 PEP 697 中添加的 API 定义——也就是说,使用一个 不带
PyObject
(或其他基类结构)开头的struct
,并使用PyObject_GetTypeData()
调用来访问内存。 - 不能创建这些类型的变量。这主要影响定义扩展模块所需的静态
PyModuleDef
变量。扩展将需要切换到 PEP 793 中添加的 API。
以下函数在实践中将变得不可用(在新的 Limited API 中),因为扩展无法为它们创建有效、静态分配的输入。为方便扩展开发人员过渡,它们尚未从 Limited API 中移除
新的导出钩子 (PEP 793)
本 PEP 的实现要求接受 PEP 793 (PyModExport
):C 扩展模块的新入口点),以提供定义扩展模块的新“导出钩子”。使用新钩子将在 Limited API 3.15 中成为强制性要求。
运行时 ABI 检查
用户——或者说他们用于构建和安装扩展的工具——将继续负责不将不兼容的扩展放在 Python 的导入路径上。这个决定是合理的,因为工具通常比 CPython 能检查的元数据丰富得多。
然而,CPython 将通过一个新的 模块槽 形式,包含基本的 ABI 信息,增加一层防御,以防止过时或配置错误的工具,或人为错误。当模块加载时,将检查此信息,并拒绝不兼容的扩展。具体细节留给 C API 工作组(参见问题 72)。
此槽将在 PEP 793 中添加的新导出钩子中成为 强制性。(该 PEP 目前说“没有必需的槽”;它将被更新。)
检查旧版 abi3
此外,在自由线程构建中,PyModuleDef_Init()
将检测使用预自由线程稳定 ABI 的扩展,当加载时发出信息性消息,并 引发异常。(实现说明:将在引发异常之前打印消息,因为尝试使用不兼容 ABI 处理异常的扩展可能会崩溃并丢失异常消息。)
这种对旧版 abi3
的检查依赖于内部位模式,如果内部对象布局需要更改,则可能在未来的 CPython 版本中移除。
abi3t
wheel 标签
使用与自由线程 CPython 构建兼容的稳定 ABI 的 Wheel 应使用新的 ABI 标签:abi3t
。选择该名称是为了反映该 ABI 与 abi3
相似,但进行了支持自由线程所需的更改(在现有特定版本 ABI 标签(如 cp314t
)中使用字母 t
)。
由于使用 Limited API 3.15 构建的 wheel 将与启用 GIL 的构建和自由线程构建兼容,因此它们应使用压缩 ABI 标签集 abi3.abi3t
。
新API
实施本 PEP 将使构建可成功加载到自由线程 Python 上的扩展成为可能,但不一定是没有 GIL 的线程安全的扩展。
允许没有 GIL 的线程安全的 Limited API——大概是 PyMutex
、PyCriticalSection
和类似的东西——将通过 C API 工作组添加,或在后续 PEP 中添加。
向后和向前兼容性
由于需要使用 PEP 793 中添加的新 PyModExport
API,Limited API 3.15 将不向后兼容旧版 CPython 版本。
无法切换的扩展作者可以继续使用 Limited API 3.14 及以下版本。为了与自由线程构建兼容,他们可以使用特定版本的 ABI 进行编译——例如,在 CPython 3.15 上编译而不定义 Py_LIMITED_API
。
Limited API 3.15 将向前兼容未来版本的 CPython 3.x。旧版 Limited API(即 3.14 及以下版本)将继续向前兼容启用 GIL 的 CPython 3.x 构建,从引入给定 Limited API 的版本开始。
兼容性概述
下表总结了 wheel 标签与 CPython 解释器的兼容性。“GIL”代表启用 GIL 的解释器;“FT”代表自由线程的解释器。
Wheel 标签 | 3.14 (GIL) | 3.14 (FT) | 3.15 (GIL) | 3.15 (FT) | 3.16+ (GIL) | 3.16+ (FT) |
---|---|---|---|---|---|---|
cp314-cp314 |
✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
cp314-cp314t |
❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
cp314-abi3 |
✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
cp315-cp315 |
❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
cp315-cp315t |
❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
cp315-abi3 |
❌ | ❌ | ✅ | ❌ | ✅ | ❌ |
cp315-abi3.abi3t |
❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
下表总结了使用给定解释器和 Py_LIMITED_API
宏构建的扩展应使用的 wheel 标签
要获取 wheel 标签... | 在...上编译 | 将 Py_LIMITED_API 设置为... |
注意 |
---|---|---|---|
cp314-cp314 |
3.14 (GIL) | (未设置) | 现有 |
cp314-cp314t |
3.14 (FT) | (未设置) | 现有 |
cp314-abi3 |
3.14+ (GIL) | PY_PACK_VERSION(3, 14) |
现有 |
cp315-cp315 |
3.15 (GIL) | (未设置) | 继续 |
cp315-cp315t |
3.15 (FT) | (未设置) | 继续 |
cp315-abi3 |
3.15+ (GIL) | PY_PACK_VERSION(3, 15) |
已停用 |
cp315-abi3.abi3t |
3.15+ (任何) | PY_PACK_VERSION(3, 15) |
新 |
注意 列中的值
- 现有:Wheel 标签目前正在使用中
- 继续:Wheel 标签延续现有方案
- 已停用:Wheel 标签延续现有方案,但会不鼓励使用。旧工具可能仍会生成它。
- 新:本 PEP 中提出。
安全隐患
未知。
如何教授此内容
需要一份移植指南来解释如何迁移到 PEP 697(扩展不透明类型的有限 C API)和 PEP 793(PyModExport
)中添加的 API。
参考实现
本 PEP 结合了若干单独实现的组件
- 不透明
PyObject
在定义_Py_OPAQUE_PYOBJECT
宏后可在 CPython 主分支中使用。在 GitHub 拉取请求 python/cpython#136505 中实现。 - 有关
PyModExport
,请参阅 PEP 793。 - 版本检查槽已在 GitHub 拉取请求 python/cpython#137212 中实现。
- 对旧版
abi3
的检查已在 GitHub 拉取请求 python/cpython#137957 中实现。 - 对于 wheel 标签,尚未有实现。
- 移植指南尚未编写。
被拒绝的想法
为自由线程添加替代的稳定 ABI
有以下可能性
- 添加一个新的稳定 ABI (“
abi3t
”),专门用于自由线程,它将与现有的abi3
不兼容。扩展无需代码更改即可针对abi3t
,构建将与自由线程 CPython (3.14 及以上) 兼容。 - 定义一个附加宏 (“
Py_OPAQUE_PYOBJECT
”),这将使PyObject
像本 PEP 中一样不透明。扩展需要像本 PEP 中一样的代码更改,并且像本 PEP 中一样,编译后的扩展 (“abi3.abi3t
”) 将与 CPython 3.15+ 的所有构建兼容。
此方案因过于复杂而被拒绝。它还将使 PyObject
的自由线程内存布局成为稳定 ABI 的一部分,从而阻止未来的调整。
与 CPython 3.14 兼容的垫片
阻止与 Python 3.14 兼容的主要问题是,由于 PyObject
和 PyModuleDef
不透明,初始化扩展模块是不可行的。解决方案 PEP 793 仅在 Python 3.15 中添加。
可以通过利用 3.14 ABI(自由线程和启用 GIL)是“冻结”的事实来解决这个问题,因此扩展可以查询正在运行的解释器,对于 3.14,使用与检测到的构建的 PyModuleDef
相对应的 struct
定义。
在这一点上,这对于 CPython 的 Limited API 来说过于繁重,难以支持和测试,但未来可能会允许。
使用 Python wheel 标签确定兼容性
本 PEP 的先前版本避免添加新的 wheel 标签 (abi3t
),并指定如果 Python 标签 是 cp315
或更高版本,则标记为 abi3
的 wheel 将与自由线程兼容。
这样的方案适用于本 PEP,但它无法表达扩展与 CPython 3.14 或更低版本的启用 GIL 和自由线程构建都兼容。添加一个新的显式标签意味着,如果 我们将来允许构建此类 wheel,打包工具应该不需要额外的更改来支持它们。它们将被标记为 cp314-abi3.abi3t
。
添加 abi4
wheel 标签
我们可以用 abi4
代替 abi3t
作为 wheel ABI 标签。“版本提升”并使用 abi4
。在 wheel 标签中,区别主要在于外观。
然而,本 PEP 没有提出的一个问题是更改 文件名 标签:扩展名将以 .abi3.so
等扩展名命名。在保持与 GIL 启用构建兼容的同时更改此名称将是一个不必要的技术更改。
在 wheel 标签中使用 abi3.abi4
而文件名中只使用 .abi3
会比 abi3.abi3t
和 .abi3
看起来更不一致。
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0803.rst