PEP 652 – 维护稳定 ABI
- 作者:
- Petr Viktorin <encukou at gmail.com>
- 讨论至:
- Discourse 帖子
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2021 年 2 月 9 日
- Python 版本:
- 3.10
- 决议:
- Python-Dev 消息
摘要
CPython 的有限 C-API 和稳定 ABI,在 PEP 384 中引入,将通过一个单一的、明确的文件进行规范化、测试和记录。
动机
PEP 384 定义了有限 API 和稳定 ABI,它们允许 CPython 的扩展器和嵌入器编译与任何后续 3.x 版本二进制兼容的扩展模块。理论上,这带来了几个优点:
- 一个模块可以针对每个平台只构建一次,并支持多个 Python 版本,从而减少构建所需的时间、功率和维护人员的精力(可能以较差的性能为代价)。
- 使用稳定 ABI 的二进制 wheel 在预发布期间与新的 CPython 版本兼容,并且可以在不适合从源代码构建的环境中进行测试。
- 作为有限 API 隐藏实现细节的一个受欢迎的副作用,此 API 正在成为备用 Python 实现的一个可行目标,这些实现将与完整的 C API 不兼容。
然而,事后看来,PEP 384 及其实现存在几个问题:
- 它定义不明确。根据 PEP 384,函数是 opt-out 的:所有未特殊标记的函数都是稳定 ABI 的一部分。实际上,对于 Windows,有一个 opt-in 列表。对于用户,有一个
#define应该只提供稳定 ABI,但没有一个流程来确保它保持最新。也没有更新文档的流程。 - 直到最近,稳定 ABI 根本没有经过测试。它往往会中断。例如,将函数更改为宏可能会破坏稳定 ABI,因为函数符号被删除了。
- 无法弃用有限 API 的部分。
- 它不完整。稳定 ABI 中缺少一些操作,除了“我们忘了”之外,几乎没有其他原因。(然而,最后一点是本 PEP 无法解决的问题。)
本 PEP 更明确地定义了有限 API,并引入了旨在使稳定 ABI 和有限 API 更有用和更健壮的流程。
基本原理
本 PEP 包含许多澄清和定义,但只有一个重大的技术变化:稳定 ABI 将在一个人为维护的“清单”文件中明确列出。
曾有努力自动收集此类列表,例如通过扫描从 Python 导出的符号。这种自动化维护起来可能比手动文件更容易,但存在主要问题:例如,导出的符号集具有平台特定的差异。此外,与更改需要永久支持的 API(或者如果 Python 3 提前结束生命周期,则直到那时)所需的总体工作量相比,更新明确清单的成本很小。
本 PEP 建议从清单 自动生成 内容:最初是文档和 DLL 内容,未来可能还会自动化测试。
稳定 ABI vs. 有限 API
PEP 384 和本文档处理 有限 API 和 稳定 ABI,这两个相关但不同的概念。简而言之:
- 稳定 ABI 承诺用 CPython 3.x 编译的某些扩展将与 CPython 3.x 的所有后续版本二进制兼容。
- 有限 API 是 CPython C API 的一个子集,用于生成此类扩展。
本节澄清了这些术语并定义了它们的一些语义(无论是预先存在的还是在此新提出的)。
“扩展”一词是所有使用 Python API 的代码的简称,例如扩展模块或嵌入 Python 的软件。
稳定的 ABI
CPython 稳定 ABI 承诺针对特定稳定 ABI 版本构建的扩展将与同一主版本的任何更新解释器兼容。
稳定 ABI 不定义完整的二进制接口:内存中结构的布局或函数调用约定等重要细节由平台、编译器及其设置决定。稳定 ABI 承诺仅适用于这些较低级别的细节也稳定的情况。
例如,使用 CPython 3.10 稳定 ABI 构建的扩展将可用于 CPython 3.11、3.12 等。它不一定与 CPython 4.0 兼容,也不一定与不同平台上的 CPython 3.10 兼容。
稳定 ABI 通常不向后兼容:使用 CPython 3.10 构建和测试的扩展通常不与 CPython 3.9 兼容。
注意
例如,从 Python 3.10 开始,Py_tp_doc 槽可以设置为 NULL,而在旧版本中,NULL 值可能会使解释器崩溃。
稳定 ABI 以性能换取其稳定性。例如,针对特定 CPython 版本构建的扩展将自动使用更快的宏而不是稳定 ABI 中的函数。
未来的 Python 版本可能会弃用稳定 ABI 的某些成员。弃用的成员仍将工作,但可能会遇到性能下降或,在最极端的情况下,内存/资源泄漏等问题。
有限 API
稳定 ABI 承诺适用于从将自身限制在 有限 API(应用程序编程接口)的代码编译的扩展。有限 API 是 CPython C API 的一个子集。
面向有限 API 的扩展应将预处理器宏 Py_LIMITED_API 定义为 3 或当前的 PYTHON_API_VERSION。这将启用几个函数的稳定 ABI 版本并将定义限制为有限 API。(但是,请注意宏并不完美:由于技术问题或疏忽,即使定义了它,也可能会暴露一些非有限 API。)
有限 API 不保证 稳定。未来,有限 API 的部分可能会被弃用。它们甚至可能被删除,只要 稳定 ABI 保持稳定并遵循 Python 的一般向后兼容性策略 PEP 387。
注意
例如,一个函数声明可能从公共头文件中删除,但保留在库中。这目前是未来的可能性;本 PEP 不提议具体的弃用和删除流程。
有限 API 的目标是涵盖与解释器交互所需的一切。不将公共 API 包含在有限子集中的主要原因应该是它需要 CPython 版本之间变化的实现细节(如结构内存布局)——通常是出于性能原因。
有限 API 不限于 CPython。鼓励其他实现实现它并帮助推动其设计。
规范
为了使稳定 ABI 更有用和更健壮,建议进行以下更改。
稳定 ABI 清单
稳定 ABI 的所有成员——函数、typedef、结构、数据、宏和常量——将明确列在单个“清单”文件 Misc/stable_abi.txt 中。
对于结构,稳定 ABI 用户允许访问的任何字段都将明确列出。
该清单还将作为有限 API 的明确列表。不属于有限 API 但属于稳定 ABI 的成员(例如 PyObject.ob_type,可通过 Py_TYPE 宏访问)将进行标注。
对于仅在某些系统上可用的项目,清单将记录决定其存在的功能宏(例如 MS_WINDOWS 或 HAVE_FORK)。为了方便实现(以及从非 C 语言使用),所有此类宏都将是简单的名称。如果未来的项目需要“负”宏或复杂表达式(例如假设的 #ifndef MACOSX 或 #if defined(POSIX) && !defined(LINUX)),将派生一个新的功能宏。
清单的格式将根据需要进行更改。它应仅由 CPython 存储库中的脚本使用。如果需要稳定列表,可以添加脚本来生成它。
以下将从 ABI 清单中生成:
- Windows 共享库的源代码
PC/python3dll.c。 - 文档输入(见下文)。
- 检查符号运行时可用性的测试用例(见下文)。
以下将作为持续集成的一部分,对照稳定 ABI 清单进行检查:
- 引用计数摘要
Doc/data/refcounts.txt包括稳定 ABI 中的所有函数(以及其他)。 - 在设置
Py_LIMITED_API时包含Python.h时声明的函数/结构和定义的常量/宏。(最初仅限 Linux;未来可能会添加对其他系统的检查。)
在初始实现之后,将添加诸如函数参数之类的细节,并检查清单的内部一致性(例如,函数签名中使用的所有类型都是 API 的一部分)。
稳定 ABI 的内容
初始稳定 ABI 清单将包括:
- PEP 384 中指定的稳定 ABI。
PC/python3dll.c中列出的所有内容。- 所有这些函数返回或作为参数的结构(结构 typedef)。(此类结构的字段不一定会添加。)
- 新的类型槽,例如
Py_am_aiter。 - 类型标志
Py_TPFLAGS_DEFAULT、Py_TPFLAGS_BASETYPE、Py_TPFLAGS_HAVE_GC、Py_TPFLAGS_METHOD_DESCRIPTOR。 - 调用约定
METH_*(已弃用的除外)。 - 宏所需的所有 API 都是稳定 ABI(标注为不属于有限 API)。
在本 PEP 获得批准时不再在 CPython 中的项目将从列表中删除。
可以根据下面的清单将其他项目添加到初始清单中。
记录有限 API
标注“有限 API 的一部分”的说明将自动添加到 Python 文档中,其方式类似于有关返回借用引用的函数的说明。
有限 API 的所有成员的完整列表也将添加到文档中。
测试稳定 ABI
将添加一个自动生成的测试模块,以确保包含在稳定 ABI 中的所有符号在编译时都可用。
更改有限 API
更改有限 API 的清单,包括添加新项目和删除现有项目,将添加到 开发者指南。该清单将 1) 提及 Python C API 设计中的最佳实践和常见陷阱,2) 指导开发人员在更改有限 API 时需要更改的文件和需要运行的脚本。
以下是清单的初始提案。(PEP 获得批准后,请参阅开发者指南获取当前版本。)
请注意,清单适用于新更改;现有 有限 API 中的几个项目是祖父级,今天无法添加。
设计考虑
- 确保更改不会破坏自 3.5 以来任何 Python 版本的稳定 ABI。
- 确保没有暴露的名称是私有的(即以下划线开头)。
- 确保新 API 已充分记录。
- 确保添加的函数的所有参数和返回值的类型以及添加的结构的所有字段都属于有限 API(或标准 C)。
- 确保新 API 及其预期用途遵循标准 C,而不仅仅是当前支持平台的特性。具体而言,遵循 PEP 7 中指定的 C 方言。
- 不要将函数指针转换为
void*(数据指针)或反之。
- 不要将函数指针转换为
- 确保新 API 遵循引用计数约定。(遵循它们使得 API 更易于理解,也更易于在其他 Python 实现中使用。)
- 不要从函数返回借用引用。
- 不要窃取函数参数的引用。
- 确保所有适用的结构字段、参数和返回值的所有权规则和生命周期明确定义。
- 考虑用户的易用性。(在 C 中,易用性本身并不重要;有用的地方是减少使用 API 所需的样板代码。错误喜欢隐藏在样板代码中。)
- 如果一个函数经常以特定参数值调用,请考虑将其设为默认值(在传入
NULL时使用)。
- 如果一个函数经常以特定参数值调用,请考虑将其设为默认值(在传入
- 考虑未来的扩展:例如,如果未来的 Python 版本可能需要向您的结构添加新字段,那将如何完成?
- 尽可能少地假设未来 CPython 版本可能更改或不同 C API 实现之间存在差异的细节:
- GIL
- 垃圾回收
- PyObject、列表/元组和其他结构的内存布局
如果遵循这些准则会损害性能,请向非有限 API 添加一个快速函数(或宏),并向有限 API 添加一个稳定的等价物。
如果任何内容不清楚,或者您有充分理由打破准则,请考虑在 capi-sig 邮件列表中讨论更改。
程序
- 将声明移动到
Include/下的头文件,放入#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03yy0000块中(其中yy对应于目标 CPython 版本)。 - 在稳定 ABI 清单
Misc/stable_abi.txt中创建一个条目。 - 使用
make regen-all重新生成自动生成的文件。(或非make平台的替代方案) - 使用
make check-abi构建 Python 并运行检查。(或非make平台的替代方案)
对扩展器和嵌入器的建议
以下注意事项将添加到文档中,同时提供有关此主题以及我们提供的保证的更好信息:
扩展作者应使用他们支持的所有 Python 版本进行测试,最好使用最低版本进行构建。
使用定义了 Py_LIMITED_API 进行编译 不能 保证您的代码符合有限 API 或稳定 ABI。Py_LIMITED_API 仅涵盖定义,但 API 还包括其他问题,例如预期的语义。
Py_LIMITED_API 不会防范的问题示例包括:
- 使用无效参数调用函数
- 在 Python 3.9 中开始接受参数
NULL值的函数,如果在 Python 3.8 下传入NULL,则会失败。只有使用 3.8(或更低版本)进行测试才能发现此问题。 - 某些结构包含一些属于稳定 ABI 的字段和不属于稳定 ABI 的其他字段。
Py_LIMITED_API不会过滤掉此类“私有”字段。 - 使用未记录为稳定 ABI 的一部分,但即使定义了
Py_LIMITED_API仍会暴露的代码,将来可能会中断。尽管团队尽了最大努力,但此类问题仍可能发生。
Python 再分发商的注意事项
稳定 ABI 承诺依赖于稳定的底层 ABI 细节,例如内存中结构的布局和函数调用约定,这些细节受编译器及其设置的影响。为了使承诺成立,这些细节在特定平台上 CPython 3.x 版本之间不得更改。
向后兼容性
向后兼容性是一个非常棒的想法!
本 PEP 旨在与现有稳定 ABI 和有限 API 完全兼容,但更明确地定义了它们的术语。它可能与对现有稳定 ABI/有限 API 的某些解释不一致。
安全隐患
未知。
如何教授此内容
技术文档将在 Doc/c-api/stable 中提供,并从 新功能 文档中链接。CPython 核心开发人员的文档将添加到开发者指南中。
参考实现
参见 问题 43795。
未来的想法
以下问题超出本 PEP 的范围,但显示了未来可能的方向。
定义弃用/删除流程
虽然本 PEP 承认未来可能会弃用或删除有限 API 的部分,但执行此操作的流程不在范围内,留给未来可能的 PEP。
ABI 清单的 C 语法
将 ABI 清单作为 C 头文件,或从清单生成头文件可能很有用。同样,两者都是未来的选择。
未解决的问题
到目前为止没有。
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源: https://github.com/python/peps/blob/main/peps/pep-0652.rst
最后修改时间: 2025-02-01 08:55:40 GMT