PEP 384 – 定义一个稳定的 ABI
- 作者:
- Martin von Löwis <martin at v.loewis.de>
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 创建日期:
- 2009年5月17日
- Python 版本:
- 3.2
- 发布历史:
摘要
目前,每个特性发布都会为 Windows 上的 Python DLL 引入新名称,并可能导致 Unix 上的扩展模块不兼容。本 PEP 建议定义一组稳定的 API 函数,这些函数保证在 Python 3 的整个生命周期内可用,并且在版本之间保持二进制兼容。只要扩展模块和嵌入 Python 的应用程序限制自己使用这个稳定的 ABI,它们就可以与不同的特性版本一起工作。
基本原理
ABI 不兼容的主要来源是内存中结构体布局的变化。例如,字符串实习(interning)的工作方式,或用于表示对象大小的数据类型,在 Python 2.x 的生命周期中都发生了变化。因此,如果扩展模块直接访问字符串、列表或元组的字段,并且在不重新编译的情况下加载到新版本的解释器中,它们就会崩溃:其他字段的偏移量可能已更改,导致扩展模块访问错误的数据。
在某些情况下,不兼容性只影响解释器的内部对象,例如帧或代码对象。例如,行号的表示方式在 2.x 生命周期中发生了变化,局部变量的存储方式也发生了变化(由于引入了闭包)。尽管大多数应用程序可能从未使用过这些对象,但更改它们需要更改 PYTHON_API_VERSION。
在 Linux 上,ABI 的变化通常不是什么大问题:系统会提供一个默认的 Python 安装,许多扩展模块已经为该版本预编译。如果需要额外的模块或额外的 Python 版本,用户通常可以在系统上自行编译它们,从而生成使用正确 ABI 的模块。
在 Windows 上,同时安装多个不同 Python 版本很常见,并且扩展模块由其作者编译,而不是由最终用户编译。为了降低 ABI 不兼容的风险,Python 目前为每个特性版本引入了一个新的 DLL 名称 pythonXY.dll,无论是否存在 ABI 不兼容性。
通过本 PEP,将有可能减少二进制扩展模块对特定 Python 特性版本的依赖,并且嵌入 Python 的应用程序可以与不同的版本一起工作。
规范
ABI 规范分为两部分:API 规范,指定哪些函数(组)可用于 ABI;以及链接规范,指定要链接哪些库。实际的 ABI(内存中结构体的布局、函数调用约定)未指定,但由编译器隐含。作为建议,为选定的平台推荐特定的 ABI。
在 Python 的演进过程中,将添加新的 ABI 函数。使用它们的应用程序将对 Python 的最低版本有要求;本 PEP 未为此类应用程序提供在 Python 库过旧时回退的机制。
术语
想要使用此 ABI 的应用程序和扩展模块统称为“应用程序”。
头文件和预处理器定义
应用程序只能包含头文件 Python.h(在包含任何系统头文件之前),或者,可选地,包含 pyconfig.h,然后是 Python.h。
在应用程序编译期间,必须定义预处理器宏 Py_LIMITED_API。这样做将隐藏所有不属于 ABI 的定义。
结构体
只有以下结构体和结构体字段可供应用程序访问
- PyObject (ob_refcnt, ob_type)
- PyVarObject (ob_base, ob_size)
- PyMethodDef (ml_name, ml_meth, ml_flags, ml_doc)
- PyMemberDef (name, type, offset, flags, doc)
- PyGetSetDef (name, get, set, doc, closure)
- PyModuleDefBase (ob_base, m_init, m_index, m_copy)
- PyModuleDef (m_base, m_name, m_doc, m_size, m_methods, m_traverse, m_clear, m_free)
- PyStructSequence_Field (name, doc)
- PyStructSequence_Desc (name, doc, fields, sequence)
- PyType_Slot (见下文)
- PyType_Spec (见下文)
这些字段的访问宏 (Py_REFCNT, Py_TYPE, Py_SIZE) 也可供应用程序使用。
以下类型可用,但为不透明(即不完整)
- PyThreadState
- PyInterpreterState
- struct _frame
- struct symtable
- struct _node
- PyWeakReference
- PyLongObject
- PyTypeObject
类型对象
类型对象的结构对应用程序不可用;不再可能声明“静态”类型对象(对于使用此 ABI 的应用程序)。相反,类型对象是动态创建的。为了方便创建类型(特别是为了能够轻松填写函数指针),以下结构和函数可用
typedef struct{
int slot; /* slot id, see below */
void *pfunc; /* function pointer */
} PyType_Slot;
typedef struct{
const char* name;
int basicsize;
int itemsize;
unsigned int flags;
PyType_Slot *slots; /* terminated by slot==0. */
} PyType_Spec;
PyObject* PyType_FromSpec(PyType_Spec*);
要指定一个槽位,必须提供一个唯一的槽位 ID。新的 Python 版本可能会引入新的槽位 ID,但槽位 ID 永远不会被回收。槽位可能会被弃用,但在整个 Python 3.x 生命周期中会继续得到支持。
槽位 ID 的命名方式与 Python 3.1 中持有指针的结构体字段名称类似,并添加了 Py_ 前缀(即 Py_tp_dealloc 而不是 tp_dealloc)
- tp_dealloc, tp_getattr, tp_setattr, tp_repr, tp_hash, tp_call, tp_str, tp_getattro, tp_setattro, tp_doc, tp_traverse, tp_clear, tp_richcompare, tp_iter, tp_iternext, tp_methods, tp_base, tp_descr_get, tp_descr_set, tp_init, tp_alloc, tp_new, tp_is_gc, tp_bases, tp_del
- nb_add nb_subtract nb_multiply nb_remainder nb_divmod nb_power nb_negative nb_positive nb_absolute nb_bool nb_invert nb_lshift nb_rshift nb_and nb_xor nb_or nb_int nb_float nb_inplace_add nb_inplace_subtract nb_inplace_multiply nb_inplace_remainder nb_inplace_power nb_inplace_lshift nb_inplace_rshift nb_inplace_and nb_inplace_xor nb_inplace_or nb_floor_divide nb_true_divide nb_inplace_floor_divide nb_inplace_true_divide nb_index
- sq_length sq_concat sq_repeat sq_item sq_ass_item sq_contains sq_inplace_concat sq_inplace_repeat
- mp_length mp_subscript mp_ass_subscript
以下字段不能在类型定义期间设置:- tp_dict tp_mro tp_cache tp_subclasses tp_weaklist tp_print - tp_weaklistoffset tp_dictoffset
typedef
除了上面列出的结构体的 typedefs 外,还有以下 typedefs 可用。它们包含在 ABI 中意味着底层类型在平台上不能改变(尽管它可能在不同平台之间有所不同)。
- Py_uintptr_t Py_intptr_t Py_ssize_t
- unaryfunc binaryfunc ternaryfunc inquiry lenfunc ssizeargfunc ssizessizeargfunc ssizeobjargproc ssizessizeobjargproc objobjargproc objobjproc visitproc traverseproc destructor getattrfunc getattrofunc setattrfunc setattrofunc reprfunc hashfunc richcmpfunc getiterfunc iternextfunc descrgetfunc descrsetfunc initproc newfunc allocfunc
- PyCFunction PyCFunctionWithKeywords PyNoArgsFunction PyCapsule_Destructor
- getter setter
- PyOS_sighandler_t
- PyGILState_STATE
- Py_UCS4
最值得注意的是,Py_UNICODE 不能作为 typedef 使用,因为同一个 Python 版本在同一个平台上可能会使用不同的定义(取决于它使用窄或宽代码单元)。需要访问 Unicode 字符串内容的应用程序可以将其转换为 wchar_t。
函数和类函数宏
默认情况下,所有函数都可用,除非它们被排除在下文之外。函数是否被文档化并不重要。
类函数宏(特别是字段访问宏)仍可供应用程序使用,但会被函数调用替换(除非它们的定义只引用 ABI 的特性,例如各种 _Check 宏)
ABI 函数声明不会改变它们的参数或返回类型。如果签名需要更改,将引入一个新的函数。如果新函数与源代码兼容(例如,如果只更改了返回类型),则可以添加一个别名宏,以便在应用程序重新编译时将调用重定向到新函数。
如果无法继续提供旧函数,它可能会被弃用,然后被移除,导致使用该函数的应用程序中断。
排除的函数
所有以 _Py 开头的函数都不可供应用程序使用。此外,所有期望应用程序不可用的参数类型(例如 PyAST_FromNode,它期望一个 node*)的函数都从 ABI 中排除。
以下头文件中声明的函数不属于 ABI
- bytes_methods.h
- cellobject.h
- classobject.h
- code.h
- compile.h
- datetime.h
- dtoa.h
- frameobject.h
- funcobject.h
- genobject.h
- longintrepr.h
- parsetok.h
- pyarena.h
- pyatomic.h
- pyctype.h
- pydebug.h
- pytime.h
- symtable.h
- token.h
- ucnhash.h
此外,期望 FILE* 的函数不属于 ABI,以避免依赖 Windows 上特定版本的 Microsoft C 运行时 DLL。
模块和类型初始化和终结函数不可用 (PyByteArray_Init、PyOS_FiniInterrupts 以及所有以 _Fini 或 _ClearFreeList 结尾的函数)。
几个处理解释器实现细节的函数不可用
- PyInterpreterState_Head, PyInterpreterState_Next, PyInterpreterState_ThreadHead, PyThreadState_Next
- Py_SubversionRevision, Py_SubversionShortBranch
PyStructSequence_InitType 不可用,因为它要求调用者提供一个静态类型对象。
Py_FatalError 将从 pydebug.h 移动到其他头文件(例如 pyerrors.h)。
可用函数的精确列表在 python3.dll 的 Windows 模块定义文件中给出 [1]。
全局变量
表示类型和异常的全局变量可供应用程序使用。此外,宏中引用的选定全局变量(例如 Py_True 和 Py_False)也可用。
全局变量定义的完整列表在 python3.def 文件中给出 [1];其中声明为 DATA 的表示变量。
其他宏
所有定义符号常量的宏都可供应用程序使用;其数值不会改变。
此外,以下宏可用
- Py_BEGIN_ALLOW_THREADS, Py_BLOCK_THREADS, Py_UNBLOCK_THREADS, Py_END_ALLOW_THREADS
缓冲区接口
缓冲区接口(Py_buffer 类型、类型槽 bf_getbuffer 和 bf_releasebuffer 等)已从 ABI 中省略,因为目前 Py_buffer 结构的稳定性尚不明确。在未来的版本中可以考虑将其纳入 ABI。
签名变更
目前有许多函数期望特定的结构体,即使调用者通常可以使用 PyObject*。这些函数已更改为期望 PyObject* 作为参数;这将导致当前显式转换为参数类型的应用程序出现警告。这些函数是 PySlice_GetIndices、PySlice_GetIndicesEx、PyUnicode_AsWideChar 和 PyEval_EvalCode。
链接
在 Windows 上,应用程序应链接 python3.dll;将提供一个导入库 python3.lib。此 DLL 将通过 /export 链接器选项将其所有 API 函数重定向到完整的解释器 DLL,即 python3y.dll。
在 Unix 系统上,ABI 通常由 Python 可执行文件本身提供。如果扩展模块使用 Py_LIMITED_API 编译,PyModule_Create 会更改为传递 3 作为 API 版本;API 版本的版本检查将接受 3 或当前的 PYTHON_API_VERSION 作为符合要求的版本。如果 Python 作为共享库编译,它将同时安装为 libpython3.so 和 libpython3.y.so;符合此 PEP 的应用程序应链接前者(扩展模块可以继续不链接 libpython 共享对象,而是依赖运行时链接)。ABI 版本符号化可用作 PYTHON_ABI_VERSION。
同样在 Unix 上,扩展模块的文件名中接受 PEP 3149 标签 abi<PYTHON_ABI_VERSION>。不执行检查以确保以这种方式命名的文件实际上仅限于受限 API,并且由于 distutils 代码冻结,不会向 distutils 添加构建此类文件的支持。
实现策略
本 PEP 将在一个分支中实现 [2],允许用户检查他们的模块是否符合 ABI。为避免用户不得不重写他们的类型定义,将提供一个脚本来转换包含类型定义的 C 源代码 [3]。
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0384.rst
最后修改时间:2025-02-01 08:55:40 GMT