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

Python 增强提案

PEP 384 – 定义一个稳定的 ABI

作者:
Martin von Löwis <martin at v.loewis.de>
状态:
最终版
类型:
标准跟踪
创建日期:
2009年5月17日
Python 版本:
3.2
发布历史:


目录

重要

本 PEP 是一个历史性文档。最新的权威文档现在可以在C API 稳定性(用户文档)和改变 Python 的 C API(开发文档)中找到。

×

有关如何提出更改,请参阅 PEP 1

摘要

目前,每个特性发布都会为 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