更改 Python 的 C API

C API 分为以下层级

  1. 内部私有 API,可用 Py_BUILD_CORE 定义。理想情况下在 Include/internal/ 中声明。任何以以下划线开头的 API 也被视为私有的。

  2. 不稳定的 C API,由 PyUnstable_ 名称前缀标识。理想情况下,在 Include/cpython/ 中与通用公共 API 一起声明。

  3. “通用”公共 C API,在正常包含 Include/Python.h 时可用。理想情况下,在 Include/cpython/ 中声明。

  4. 受限 C API,在定义 Py_LIMITED_API 时可用。理想情况下,直接在 Include/ 下声明。

在添加或更改其中的定义时,每一层都有不同的稳定性和维护要求需要考虑。

公共 C API 的兼容性保证在用户文档 Doc/c-api/stable.rst (C API 稳定性) 中进行了说明。

内部 API

内部 API 在 Include/internal/ 中定义,并且仅可用于构建 CPython 本身,如 Py_BUILD_CORE 等宏所示。

虽然内部 API 可以随时更改,但保持其稳定性仍然很重要:其他 API 或其他 CPython 开发人员可能依赖它。对于用户而言,内部 API 有时是解决棘手问题最好的解决方法——尽管这些用例应该在 C API Discourse 类别 或问题中进行讨论,以便我们可以尝试找到一种支持的方式来为他们服务。

使用 PyAPI_FUNC 或 PyAPI_DATA

Include/internal/ 中使用 PyAPI_FUNCPyAPI_DATA 定义的函数或结构是仅针对特定用例(如调试器和分析器)公开的内部函数。理想情况下,这些应该迁移到 不稳定的 C API

使用 extern 关键字

Include/internal/ 中使用 extern 关键字定义的函数不得且不能在 CPython 代码库外部使用。只有内置 stdlib 扩展(使用定义的 Py_BUILD_CORE_BUILTIN 宏构建)才能使用此类函数。

如有疑问,应使用 extern 关键字在 Include/internal 中定义新的内部 C 函数。

私有名称

任何以以下划线开头的 API 也被视为内部 API。当前,只有一种主要用例使用此类名称,而不是将定义放入 Include/internal/(或直接放入 .c 文件中)

  • 其他公共 API 的内部帮助器,用户不应直接调用。

请注意,从历史上看,下划线用于更好地由 不稳定的 C API 提供服务的 API;

  • “临时”API,包含在 Python 版本中以测试新 API 的实际使用情况;

  • 用于非常专门的用途(如 JIT 编译器)的 API。

内部 API 测试

内部 C API 的 C 测试位于 Modules/_testinternalcapi.c 中。命名为 test_* 的函数直接用作测试。测试的 Python 部分位于 Lib/test 中的各个位置。

公共 C API

当正常包含 Python.h(即不定义宏来选择其他变体)时,CPython 的公共 C API 可用。

它应该在 Include/cpython/ 中定义(除非是受限 API 的一部分,请参见下文)。

扩展/更改公共 API 的指南

  • 确保新 API 遵循引用计数约定。(遵循它们使 API 更容易推理,并且更容易在其他 Python 实现中使用。)

    • 函数不得窃取引用

    • 函数不得返回借用引用

    • 返回引用的函数必须返回强引用

  • 确保所有适用的结构字段、参数和返回值的所有权规则和生命周期都已明确定义。

  • 返回 PyObject * 的函数必须在成功时返回一个有效的指针,并在出错时返回 NULL 并引发异常。大多数其他 API 必须在出错时返回 -1 并引发异常,并在成功时返回 0

  • 具有较小和较大结果的 API 必须为较小结果返回 0,为较大结果返回 1。考虑具有三向返回的查找函数

    • return -1:内部错误或 API 误用;引发异常

    • return 0:查找成功;未找到项目

    • return 1:查找成功;找到项目

如果这些准则不适用于你的 API,请发起公开讨论。

注意

返回值是指C 返回语句返回的值。

C API 测试

公共 C API 的测试位于 _testcapi 模块中。名为 test_* 的函数直接用作测试。需要 Python 代码(或仅在 Python 中更容易部分编写)的测试位于 Lib/test 中,主要位于 Lib/test/test_capi 中。

由于其大小,_testcapi 模块在多个源文件中定义。要添加一组新测试(或从整体 Modules/_testcapimodule.c 中提取一组),

  • 创建一个名为 Modules/_testcapi/yourfeature.c 的 C 文件

  • 该文件应像往常一样定义一个模块,但

    • 不要包含 <Python.h>,而是包含 "parts.h"

    • 不要使用 PyInit_modname,而是定义一个 _PyTestCapi_Init_yourfeature 函数,该函数获取 _testcapi 模块并向其添加函数/类。(你可以使用 PyModule_AddFunctions 来添加函数。)

  • _PyTestCapi_Init_* 函数添加到 Modules/_testcapi/parts.h

  • Modules/_testcapimodule.c 中的 PyInit__testcapi 调用 _PyTestCapi_Init_*

  • 将新的 C 文件添加到 Modules/Setup.stdlib.inPCbuild/_testcapi.vcxprojPCbuild/_testcapi.vcxproj.filters 中,与其他 _testcapi/*.c 条目一起。

请注意,所有 Modules/_testcapi/*.c 源都初始化同一个模块,因此请小心名称冲突。

在移动现有测试时,可以随意将 TestError 替换为 PyExc_AssertionError,除非实际测试自定义异常。

不稳定的 C API

不稳定的 C API 层级适用于需要与解释器紧密集成的扩展,例如调试器和 JIT 编译器。此层级的用户可能需要在每次功能版本发布时更改其代码。

在很多方面,此层级类似于常规 C API

  • 在正常包含 Python.h 时可用,

  • 应在 Include/cpython/ 中定义,

  • 它需要测试,因此我们不会无意中破坏它

  • 它需要文档,以便我们和用户可以就预期行为达成一致,

  • 它以相同的方式进行测试和记录。

区别在于

  • 函数、结构、宏等的名称以 PyUnstable_ 前缀开头。这定义了不稳定层级中的内容。

  • 不稳定的 API 可以在功能版本中更改,而无需任何弃用期。

  • 稳定性说明会出现在文档中。这会根据名称(通过 Doc/tools/extensions/c_annotations.py)自动发生。

尽管“不稳定”,但有一些规则可以确保第三方代码可以可靠地使用此 API

将 API 从公共层级移至不稳定层级

  • 使用 PyUnstable_ 前缀以新名称公开 API。所有符号(函数、宏、变量等)都必须使用 PyUnstable_ 前缀。

  • 使旧名称成为别名(例如调用新函数的 static inline 函数)。

  • 弃用旧名称,通常使用 Py_DEPRECATED

  • 在“新增功能”中宣布更改。

旧名称应继续可用,直到进行不兼容的更改。根据 Python 的向后兼容性策略(PEP 387),此弃用需要持续至少两个版本(指导委员会例外情况除外)。

对于在添加官方不稳定层级之前(Python 3.12 版本)的 Python 版本中引入的 API,规则会放宽。对于在 Python 3.12 之前引入的 API,您可以进行不兼容的更改(并删除旧名称),就好像该函数已经是以下内容的不稳定层级的一部分

  • 记录的稳定性低于默认值。

  • 以下划线开头命名。

将 API 从私有层级移至不稳定层级

  • 使用 PyUnstable_ 前缀以新名称公开 API。

  • 如果旧名称有文档记录或在外部广泛使用,请使其成为别名并弃用它(通常使用 Py_DEPRECATED)。它应继续可用,直到进行不兼容的更改,就好像它以前是公开的一样。

    即使是带下划线的名称也适用。Python 并不总是严格遵循前导下划线。

  • 在最新动态中宣布更改。

将 API 从不稳定移动到公开

  • 以新名称公开 API,不带 PyUnstable_ 前缀。

  • 使旧 PyUnstable_* 名称成为别名(例如调用新函数的 static inline 函数)。

  • 在最新动态中宣布更改。

旧名称应保持可用,直到新公共名称被弃用或删除。无需弃用旧名称(它一开始就不稳定),但也没有必要仅仅因为某个函数现在已准备好面向更广泛的受众而破坏正常工作的代码。

受限 API

受限 API 是 C API 的一个子集,旨在保证 Python 3 版本之间的 ABI 稳定性。定义宏 Py_LIMITED_API 将公开 API 限制为该子集。

不允许破坏稳定 ABI 的任何更改。

受限 API 应在 Include/ 中定义,不包括 cpythoninternal 子目录。

更改受限 API 和从中删除项的准则

虽然稳定 ABI 不得被破坏,但现有的受限 API 可以更改,并且可以从中删除项,如果

  • 遵循向后兼容性政策(PEP 387),并且

  • 稳定 ABI 没有被破坏——也就是说,使用旧版 Python 的受限 API 编译的扩展在较新版本的 Python 上继续工作。

这很难做到,需要仔细考虑。一些示例

  • 任何版本的受限 API 中通过宏访问的函数、结构等是稳定 ABI 的一部分,即使它们以下划线命名。不得删除它们,并且它们的签名不得更改。(但它们的实现可能会更改。)

  • 如果结构成员是任何版本的受限 API 的一部分,则不能重新排列它们。

  • 如果受限 API 允许用户直接分配结构,则其大小不得更改。

  • 导出符号(函数和数据)必须继续作为导出符号提供。具体来说,只有当 Python 也继续提供实际函数时,函数才能转换为 static inline 函数(或宏)。例如,请参阅 Py_NewRef 和 3.10 中的 重新定义

可以删除标记为稳定 ABI 一部分的项,但前提是无法在任何过去版本的受限 API 中使用它们。

添加到受限 API 的准则

  • 适用于一般 公共 C API 的准则。请参阅 扩展/更改公共 API 的准则

  • 仅当 Py_LIMITED_API 设置为添加 API 的版本或更高版本时,才应定义新的受限 API。(请参阅以下内容以获取正确的 #if 保护。)

  • 所有参数类型、返回值、结构成员等都需要成为受限 API 的一部分。

    • 不应添加处理 FILE*(或具有 ABI 可移植性问题的其他类型)的函数。

  • 定义宏时三思而后行。

    • 宏不应公开实现细节

    • 函数必须作为实际函数导出,而不仅仅是类函数的宏。

    • 如果可能,请避免使用宏。这使得受限 API 在不使用 C 预处理器的语言中更易于使用。

  • 在扩展受限 API 之前,请开始公开讨论

  • 受限 API 必须遵循标准 C,而不仅仅是当前支持平台的功能。确切的 C 方言在 PEP 7 中进行了描述。

    • 文档示例(更一般地:API 的预期用途)也应遵循标准 C。

    • 尤其是,不要将函数指针转换为 void*(数据指针),反之亦然。

  • 考虑用户的易用性。

    • 在 C 中,易用性本身并不重要;有用的是减少使用 API 所需的样板代码。错误喜欢隐藏在样板中。

    • 如果某个函数经常使用某个参数的特定值调用,请考虑将其设为默认值(在传入 NULL 时使用)。

    • 受限 API 需要有良好的文档说明。

  • 考虑未来的扩展

    • 如果未来的 Python 版本可能需要向你的结构添加一个新字段,请确保可以做到这一点。

    • 对可能在未来的 CPython 版本中发生更改或在 C API 实现中有所不同的实现细节尽可能少地进行假设。最重要的 CPython 特定实现细节涉及

      • GIL

      • 垃圾回收

      • PyObject、列表/元组和其他结构的内存布局

如果遵循这些准则会损害性能,请向非受限 API 添加一个快速函数(或宏),并向受限 API 添加一个稳定的等效项。

如果有什么不清楚的地方,或者你有充分的理由违反这些准则,请考虑在 capi-sig 邮件列表中讨论更改。

向受限 API 添加新定义

  • 将声明添加到 Include/ 下的标头文件中,将其放入受以下内容保护的块中

    #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03yy0000
    

    其中 yy 对应于目标 CPython 版本,例如 Python 3.10 的 0x030A0000

  • 将一个条目追加到稳定 ABI 清单 Misc/stable_abi.toml

  • 使用 make regen-limited-abi 重新生成自动生成的文件。在没有 make 的平台上,直接运行此命令

    ./python ./Tools/build/stable_abi.py --generate-all ./Misc/stable_abi.toml
    
  • 构建 Python 并使用 make check-limited-abi 进行检查。在没有 make 的平台上,直接运行此命令

    ./python ./Tools/build/stable_abi.py --all ./Misc/stable_abi.toml
    
  • 添加测试——见下文。

受限 API 测试

由于受限 API 是 C API 的一个子集,因此无需测试各个函数的行为。相反,测试可以验证使用公开的子集是否可以完成某些任务,或者使用从当前受限 API 中移除但仍需要支持较旧受限 API/稳定 ABI 版本的功能。

添加测试文件

  • 添加 C 文件 Modules/_testcapi/yourfeature_limited.c。如果该文件已存在,但其 Py_LIMITED_API 版本过低,请添加一个版本后缀,例如 Python 3.12+ 的 yourfeature_limited_3_12.c

  • #define Py_LIMITED_API 为所需的最小受限 API 版本。

  • #include "parts.h"Py_LIMITED_API 定义之后

  • 将文件的其余部分全部包含在 #ifdef LIMITED_API_AVAILABLE 中,以便在不兼容的构建中跳过它。

  • 按照 C API 测试 的一般说明进行操作。所有添加都放在 #ifdef LIMITED_API_AVAILABLE 保护的节中。

Lib/test 中的 Python 测试使用 test.support.requires_limited_api 装饰器,以便在不兼容的构建中跳过它们。