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 不兼容的主要来源是内存中结构布局的更改。例如,字符串驻留的工作方式或用于表示对象大小的数据类型在 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
typedefs
除了上面列出的结构体的 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 开头的函数都不可用于应用程序。此外,所有期望应用程序不可用的参数类型的函数都从 ABI 中排除,例如 PyAST_FromNode(它期望一个 node*
)。
以下头文件中声明的函数不是 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 代码冻结,不会添加对构建此类文件的支持。
实现策略
此 PEP 将在一个分支 [2] 中实现,允许用户检查其模块是否符合 ABI。为了避免用户不得不重写其类型定义,将提供一个用于转换包含类型定义的 C 源代码的脚本 [3]。
参考文献
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0384.rst
上次修改时间:2023-09-09 17:39:29 GMT