Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 809 – 未来稳定的ABI

作者:
Steve Dower <steve.dower at python.org>
讨论至:
Discourse 帖子
状态:
草案
类型:
标准跟踪
要求:
703, 793, 697
创建日期:
2025年9月19日
Python 版本:
3.15
发布历史:
2025年9月30日

目录

摘要

稳定的ABI(即 abi3)将不再保留,需要替换。abi2026 将是第一个替代品,它将解决当前已知的兼容性问题,并计划在至少10年后退役。下一个ABI(例如 abi2031)将与前一个ABI至少有五年的重叠期。

长期稳定性将通过运行时ABI发现机制实现,允许扩展在支持相同ABI的早期版本中运行。ABI生命周期内的更改和添加可以作为“接口”添加,从而可以在运行时发现它们,以便调用者可以选择合适的备用行为。目前,此类添加会阻止扩展在早期运行时加载。

在启用GIL的构建中,abi3 ABI将保留至少五年,之后可能会退役(届时只有 abi2026 及更高版本可用)。启用GIL的构建有可能在此之前完全退役。无 GIL 构建没有 abi3,因此它们的第一个稳定ABI将是 abi2026

术语

本PEP使用“启用GIL的构建”作为“无 GIL 构建”的反义词,即未通过 Py_GIL_DISABLED 构建的解释器或扩展。

动机

当前,无 GIL 构建不支持稳定ABI。当定义了 Py_LIMITED_API 时,扩展将无法构建。同样,为CPython的启用GIL构建的扩展将无法在无 GIL 构建中加载(或崩溃)。

接受PEP 779的帖子中,指导委员会表示“希望为Python 3.15准备并定义无 GIL 的稳定ABI”。

本PEP提出了一个稳定ABI,它将兼容3.15及更高版本的所有变体,允许包开发人员为其扩展生成一个单独的构建。

背景

Python的稳定ABI,如PEP 384PEP 652中所定义,提供了一种编译扩展模块的方法,这些模块可以在CPython解释器的多个次要版本上加载。一些项目使用它来限制每个版本需要构建和分发的wheel(二进制工件)的数量,和/或使其更容易使用Python的预发布版本进行测试。

随着无 GIL 构建 (PEP 703) 有望最终成为默认设置 (PEP 779),我们需要一种方法来使稳定ABI可用于这些构建。

要针对稳定ABI构建,扩展必须使用“受限API”,即仅使用CPython暴露的函数、结构等的一部分。受限API是版本化的,针对受限API 3.X构建会生成一个与CPython 3.X和“任何”更高版本ABI兼容的扩展(尽管CPython中的错误有时会在实践中导致不兼容)。此外,受限API不是“稳定”的:新版本可能会删除旧版本中可用的API项。

本PEP提议对受限API和稳定ABI的版本控制进行重大更改。目标是实现稳定性和兼容性的长期管理,同时还允许受限子集的用户访问更高版本的Python版本中的创新。

基本原理

本PEP中的设计做出了以下假设:

一个ABI
单个编译的扩展模块应支持无 GIL 和启用 GIL 的构建。
不向后兼容
CPython 3.14及以下版本不支持新的受限API。需要此支持的项目可以专门为3.14无 GIL 解释器和较旧的稳定ABI版本构建单独的扩展。
API更改没问题
新的受限API可能要求扩展作者对其代码进行重大更改。无法(目前)执行此操作的项目可以继续使用受限API 3.14,这将生成仅与启用GIL的构建兼容的扩展。
无需额外配置
我们不会引入影响可用API和ABI兼容性的新“旋钮”。

规范

请注意,大部分规范与PEP 803相同,读者应参考该提案以获取详细信息。ABI稳定性、构建时宏和接口API部分是本提案特有的。

ABI稳定性

稳定ABI将冻结至少10年。当新版本的稳定ABI冻结时,现有版本将继续支持至少5年。这为包维护者(和其他用户)提供了充足的迁移时间,可以同时迁移其所有支持的版本。但是,如果Python核心开发团队认为没有理由替换当前的稳定ABI,则可以推迟冻结新版本。

新的稳定ABI通过PEP流程定义,其名称反映了支持它的第一个运行时发布年份。

当稳定ABI冻结时,年份将成为ABI的名称。例如,我们预计此方案下的第一个ABI将是abi2026,并将得到所有版本至少到2036年的支持。如果支持在2036年停止,那么abi2031将是迁移目标,这允许包开发人员在其自己的迁移后支持至少五年的版本。

在冻结期间,不允许进行任何ABI更改。不允许添加、删除、修改或大幅语义更改。至关重要的是,针对特定ABI编译的扩展模块必须在支持该ABI的任何Python版本(无论是早期还是后期)上成功加载(即,所有导入的符号在所有支持的平台上都得到满足)。

不允许通过现有兼容ABI在运行时检测不到的语义更改。也就是说,用于检测当前Python版本上是否预期特定行为的API必须在支持该ABI的所有早期版本上都可用。

不透明PyObject

受限API的3.15版本将使许多结构变得不透明,以便它们的用户无法对其大小或布局做出任何假设。详细信息可在PEP 803中找到,此处提出的方案是相同的。

新的导出钩子 (PEP 793)

本PEP的实施要求PEP 793PyModExport:C扩展模块的新入口点)被接受,它将提供一个新的“导出钩子”来定义扩展模块。在受限API 3.15中,使用新钩子将成为强制要求。

本提案与PEP 803的提案相同。

运行时ABI检查

详情请参阅PEP 803。本提案与此相同。

构建时宏

我们要求将 Py_LIMITED_API 定义为 0x03ff_YYYY——也就是说,高位字是一个常量 0x03ff,而低位字是ABI名称(年份)的十六进制值。虽然这会导致十进制值与年份不符,但我们认为这并不重要,因为该值是一个任意标签,并且更可能被指定为常量(在 cc 命令行中),而不是计算值。

使用 0x03ff 作为常量的目的是为了与早期运行时兼容。当与仅支持 abi3 的头文件一起使用时,相同的常量将选择该版本中可用的“最完整”的ABI3版本。例如,在3.15+中使用 0x03ff2026 将选择 abi2026,而在3.10中将选择适用于3.10-3.14的ABI3版本。

Wheel标签

Wheel应使用ABI标签abi2026进行标记。无需更改Python或平台标签。可能值得注意的是,标记为cp314或更早版本的发布永远不会与abi2026兼容,因为它当时不存在,因此标记为py3-abi2026-<plat>的wheel不会导致使用新稳定ABI的wheel被旧版本加载。

新API

实施此PEP将使得构建能够在无 GIL 的 Python 上成功加载的扩展成为可能,但不一定能构建在无 GIL 情况下线程安全的扩展。

允许在无 GIL 情况下实现线程安全的受限API——可能包括 PyMutexPyCriticalSection 等——将通过C API工作组或在后续PEP中添加。

接口API

Python 和新的受限 API 将添加一个新的“接口”API。此 API 旨在满足上述 ABI 稳定性部分中“所有版本上的语义更改都是可检测的”要求。也就是说,使用者[1]将能够立即采用新的 API,使用最新版本编译受限 API,并为支持该 ABI 的所有版本保留二进制兼容性。

简而言之,主要的 API 是 PyObject_GetInterface(),它委托给一个新的仅限原生类型的槽位,以填充包含数据或函数指针的C结构体。因为C结构体定义嵌入到扩展中,而不是在运行时获取,所以扩展模块在运行不支持它的Python版本时也可以了解后来的结构体。

如果调用 PyObject_GetInterface 请求的结构在当前版本上不可用,或者对于提供的对象不可用,则调用安全失败。调用者可以根据自己的偏好使用回退逻辑(例如,使用抽象的Python API)或中止。

例如,如果在 abi2026 的生命周期内添加了一个新的 API,它允许更高效地访问 int 对象的内部数据,那么我们不会添加新的 API,而是会创建一个新的接口:一个包含将数据复制到新位置的函数指针的结构体,以及该接口之前未使用的索引/名称。调用者可以首先调用 PyObject_GetInterface(int_object, &intf_struct);如果成功,则调用(假设的)(*intf_struct.copy_bits)(&intf_struct, dest, sizeof(dest));如果失败,他们可以使用 PyObject_CallMethod(int_object, "to_bytes", ...) 执行相同的操作,但效率较低。此示例的最终结果是一个扩展模块,它与支持 abi2026 的“所有”版本二进制兼容,但在运行较新版本的 Python 时效率更高。

概述完毕,以下是每个新API的完整规范

// Abstract API to request an interface for an object (or type).
PyAPI_FUNC(int) PyObject_GetInterface(PyObject *obj, void *intf);

// API to release an interface.
PyAPI_FUNC(int) PyInterface_Release(void *intf);

// Expected layout of the start of each interface. Actual interface structs
// will add additional function pointers or data.
typedef struct PyInterface_Base {
    // sizeof(self), for additional validation that the caller is passing
    // the correct structure.
    Py_ssize_t size;

    // Unique identifier for the struct. Details below.
    uint64_t name;

    // Function to release the struct (e.g. to decref any PyObject fields).
    // Should only be invoked by PyInterface_Release(), not directly.
    int (*release)(struct PyInterface_Base *intf);
} PyInterface_Base;

// Type slot definition for PyTypeObject field.
typedef int (*Py_getinterfacefunc)(PyObject *o, PyInterface_Base *intf);

结构体的唯一标识符是一个64位整数,定义为宏(以确保编译的扩展模块嵌入该值,而不是在运行时尝试发现它)。高32位是命名空间,实现者定义自己的结构体时应为自己选择一个唯一的值。零保留给CPython。

接口名称用于标识结构体布局,因此任何定义的相同接口名称的对象都可以重用来自其他命名空间的接口名称,只要结构体匹配即可。这是故意的,因为它允许第三方类型实现与核心类型相同的接口,而无需依赖共享实现。需要明确的是,为CPython定义的接口可以被其他扩展模块使用,而无需更改名称或名称的命名空间。

例如,考虑一个假想的接口来实现 PyDict_GetItemString()。核心 dict 类型可能会进行内部优化以通过字符串键定位条目,而外部类型可以使用相同的接口进行自己的优化。对于调用者来说,它似乎使用了相同的接口,因此调用者与比使用(例如)CPython的具体对象API更广泛的类型兼容。

接口名称在任何时候都不能从头文件中删除,并且结构定义只有在支持它们的所有稳定ABI版本完全退役后才能删除。然而,如果某个接口不再推荐或可靠,对象可能会停止返回它,即使早期版本确实返回了它们。如果合适,可以使用运行时弃用警告,没有指定任何特定规则。

接口结构是固定的,不能更改。当需要更改时,应使用新名称定义一个新接口。添加到结构中的接口字段是公共API,应予以文档化。不打算直接使用的字段应以下划线开头,但不能被“私有化”。接口可以提供数据和函数指针的混合,或使用强 PyObject * 引用以避免竞争条件。

检索接口后,接口必须保持有效,直到释放,即使对象的引用已释放。接口的行为可能会根据需要处理对底层对象的更改,但可能应该记录其选择。拥有两个以不同方式处理此类更改的相似接口(例如,一个接口在接口的生命周期内锁定对象,而另一个不锁定)并非不合理。

添加新的受限API的过程有所改变:不再是每个版本都增长的ABI,新的API可以作为未在使用受限API时的真实函数添加,但应作为受限API的静态内联函数添加。此静态内联函数应使用接口在运行时检测功能,并包含抽象回退或适当的异常。

这意味着使用者可以立即采用新的API,使用最新版本编译受限API,并为所有支持相同稳定ABI的版本保留二进制兼容性。

在下一次稳定ABI冻结时,API可以被提升为新的稳定ABI/受限API作为真实函数,或保留为接口。

向后兼容性

受限API 3.15 将由于移除了结构和函数而无法向后兼容旧版 CPython。

无法切换的扩展作者可以继续使用受限API 3.14及以下版本,以用于启用GIL的构建。

在启用GIL的构建中,abi3 不会进行任何更改,并且所有现有符号将保持可用,尽管这些符号在新稳定ABI下不再可用。

使无 GIL 构建成为 CPython 的默认/唯一发布将是一个向后不兼容的更改,扩展作者将需要进行迁移。

安全隐患

未知。

如何教授此内容

Python 的原生 ABI 可以描述为一种定期更新的标准或规范,以年份标识,类似于其他语言。任何扩展模块都可以使用此 ABI,并在其分发信息中声明它们预期的 ABI。任何 Python 实现都可以选择支持特定的 ABI 版本,并且任何也支持该版本的扩展都应该可用。

abi3 迁移到新的 ABI 可能涉及源代码更改,但可以视为一次性任务。在许多(如果不是大多数)情况下,源代码将与 abi3 和新的 ABI 兼容,从而简化了旧版本和当前版本的构建生产。通常,abi3 构建应使用最旧支持的 CPython 运行时构建,而新的 ABI 构建应使用最新的 CPython 运行时(或另一个兼容的运行时)构建。

从一个 ABI(例如 abi2026)迁移到下一个 ABI(例如 abi2031)应该是一个手动任务。ABI 更新之间有足够的重叠,大多数项目只需要同时支持一个,并且如果它们自己的支持矩阵允许,可以一次性更新所有构建。对包维护者立即支持每个新 ABI 没有期望。

通过动态接口检测确保前向和后向兼容性。使用最近添加的受限API函数的代码将在旧版本上运行,尽管性能可能会较低。有关任何受限API特定细节的信息,请参阅新函数的文档。

非 C 调用者应直接使用接口机制来访问新功能,而不会人为地将其兼容性限制为较新的版本。接口的名称和结构布局保证始终稳定,但不能假设接口将始终可用,并且应包含适当的回退代码(替代实现或错误处理)。

参考实现

请参阅PEP 803以获取从该PEP继承的方面的参考实现链接。

接口的参考实现是zooba/cpython#44

被拒绝的想法

[目前请参阅讨论。]

未解决的问题

[目前请参阅讨论。]

脚注


来源:https://github.com/python/peps/blob/main/peps/pep-0809.rst

最后修改:2025-10-01 17:55:07 GMT