引言¶
Python 的应用程序编程接口 (API) 允许 C 和 C++ 程序员在不同层面访问 Python 解释器。该 API 同样适用于 C++,但为简洁起见,通常将其称为 Python/C API。使用 Python/C API 有两个根本不同的原因。第一个原因是为了编写特定用途的扩展模块;这些是扩展 Python 解释器的 C 模块。这可能是最常见的用途。第二个原因是将 Python 作为更大应用程序的组件;这种技术通常被称为在应用程序中嵌入 Python。
编写扩展模块是一个相对成熟的过程,其中“食谱”方法效果很好。有几种工具可以在一定程度上自动化这个过程。虽然人们从 Python 早期就将其嵌入到其他应用程序中,但嵌入 Python 的过程不如编写扩展模块那么直接。
许多 API 函数无论您是嵌入还是扩展 Python 都很有用;此外,大多数嵌入 Python 的应用程序也需要提供自定义扩展,因此在尝试将 Python 嵌入到实际应用程序中之前,熟悉如何编写扩展模块可能是一个好主意。
语言版本兼容性¶
Python 的 C API 与 C11 和 C++11 版本的 C 和 C++ 兼容。
这是一个下限:C API 不要求使用更高版本的 C/C++ 功能。您不需要启用编译器的“c11 模式”。
编码标准¶
如果您正在编写要包含在 CPython 中的 C 代码,则必须遵循 PEP 7 中定义的指南和标准。这些指南适用于您贡献的 Python 版本。遵循这些约定对于您自己的第三方扩展模块来说不是必需的,除非您最终希望将它们贡献给 Python。
头文件¶
使用 Python/C API 所需的所有函数、类型和宏定义都通过以下行包含在您的代码中
#define PY_SSIZE_T_CLEAN
#include <Python.h>
这意味着包含了以下标准头文件:<stdio.h>
、<string.h>
、<errno.h>
、<limits.h>
、<assert.h>
和 <stdlib.h>
(如果可用)。
备注
由于 Python 可能会定义一些预处理器定义,这些定义会影响某些系统上的标准头文件,因此您必须在包含任何标准头文件之前包含 Python.h
。
建议在包含 Python.h
之前始终定义 PY_SSIZE_T_CLEAN
。有关此宏的描述,请参阅解析参数和构建值。
Python.h 定义的所有用户可见名称(除了包含的标准头文件定义的名称)都带有前缀 Py
或 _Py
。以 _Py
开头的名称供 Python 实现内部使用,扩展作者不应使用。结构成员名称没有保留前缀。
备注
用户代码不应定义以 Py
或 _Py
开头的名称。这会混淆读者,并危及用户代码向未来 Python 版本的可移植性,因为未来版本可能会定义更多以这些前缀开头的名称。
头文件通常与 Python 一起安装。在 Unix 上,它们位于目录 prefix/include/pythonversion/
和 exec_prefix/include/pythonversion/
中,其中 prefix
和 exec_prefix
由 Python 的 configure 脚本的相应参数定义,而 version 是 '%d.%d' % sys.version_info[:2]
。在 Windows 上,头文件安装在 prefix/include
中,其中 prefix
是安装程序指定的安装目录。
要包含头文件,请将这两个目录(如果不同)都放在编译器的包含搜索路径上。请不要将父目录放在搜索路径上,然后使用 #include <pythonX.Y/Python.h>
;这在多平台构建上会中断,因为 prefix
下的平台无关头文件包含来自 exec_prefix
的平台特定头文件。
C++ 用户应注意,尽管 API 完全使用 C 定义,但头文件正确地将入口点声明为 extern "C"
。因此,从 C++ 使用 API 不需要做任何特殊的事情。
有用宏¶
Python 头文件中定义了几个有用的宏。许多宏的定义更靠近它们有用的地方(例如,Py_RETURN_NONE
、PyMODINIT_FUNC
)。其他更通用的实用宏在此处定义。这不一定是一个完整的列表。
-
Py_ABS(x)¶
返回
x
的绝对值。在 3.3 版本加入。
-
Py_ALWAYS_INLINE¶
请求编译器始终内联静态内联函数。编译器可以忽略它并决定不内联该函数。
当在禁用函数内联的调试模式下构建 Python 时,它可用于内联性能关键的静态内联函数。例如,MSC 在调试模式下构建时会禁用函数内联。
盲目地用 Py_ALWAYS_INLINE 标记静态内联函数可能会导致性能下降(例如由于代码大小增加)。编译器通常比开发人员更擅长成本/收益分析。
如果 Python 是在调试模式下构建的(如果定义了
Py_DEBUG
宏),则Py_ALWAYS_INLINE
宏不起作用。它必须在函数返回类型之前指定。用法
static inline Py_ALWAYS_INLINE int random(void) { return 4; }
在 3.11 版本中新增。
-
Py_CHARMASK(c)¶
参数必须是一个字符或一个在 [-128, 127] 或 [0, 255] 范围内的整数。此宏返回转换为
unsigned char
的c
。
-
Py_DEPRECATED(version)¶
用于已弃用的声明。宏必须放置在符号名称之前。
示例
Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);
版本 3.8 中的变化: 添加了 MSVC 支持。
-
Py_GETENV(s)¶
类似于
getenv(s)
,但如果在命令行上传递了-E
则返回NULL
(参阅PyConfig.use_environment
)。
-
Py_MAX(x, y)¶
返回
x
和y
之间的最大值。在 3.3 版本加入。
-
Py_MEMBER_SIZE(type, member)¶
以字节为单位返回结构体 (
type
)member
的大小。在 3.6 版本加入。
-
Py_MIN(x, y)¶
返回
x
和y
之间的最小值。在 3.3 版本加入。
-
Py_NO_INLINE¶
禁用函数内联。例如,它减少了 C 栈消耗:在大量内联代码的 LTO+PGO 构建中很有用(参阅 bpo-33720)。
用法
Py_NO_INLINE static int random(void) { return 4; }
在 3.11 版本中新增。
-
Py_STRINGIFY(x)¶
将
x
转换为 C 字符串。例如,Py_STRINGIFY(123)
返回"123"
。在 3.4 版本加入。
-
Py_UNREACHABLE()¶
当您有一个按设计无法访问的代码路径时,请使用此宏。例如,在
switch
语句的default:
子句中,所有可能的值都在case
语句中涵盖。在您可能想放置assert(0)
或abort()
调用的地方使用此宏。在发布模式下,此宏有助于编译器优化代码,并避免关于不可达代码的警告。例如,在发布模式下,GCC 上的宏是通过
__builtin_unreachable()
实现的。Py_UNREACHABLE()
的一个用途是跟在一个永远不会返回但未声明为_Py_NO_RETURN
的函数的调用之后。如果代码路径不太可能但可能在异常情况下达到,则不得使用此宏。例如,在内存不足的情况下或系统调用返回超出预期范围的值。在这种情况下,最好向调用者报告错误。如果无法向调用者报告错误,可以使用
Py_FatalError()
。在 3.7 版本加入。
-
Py_UNUSED(arg)¶
在函数定义中,使用此宏来消除未使用的参数的编译器警告。示例:
int func(int a, int Py_UNUSED(b)) { return a; }
。在 3.4 版本加入。
-
PyDoc_STRVAR(name, str)¶
创建一个名为
name
的变量,可在文档字符串中使用。如果 Python 在没有文档字符串的情况下构建,则该值将为空。使用
PyDoc_STRVAR
作为文档字符串,以支持在没有文档字符串的情况下构建 Python,如 PEP 7 中指定。示例
PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element."); static PyMethodDef deque_methods[] = { // ... {"pop", (PyCFunction)deque_pop, METH_NOARGS, pop_doc}, // ... }
对象、类型和引用计数¶
大多数 Python/C API 函数都有一个或多个类型为 PyObject* 的参数和返回值。这种类型是指向一个不透明数据类型的指针,该类型表示任意 Python 对象。由于在大多数情况下(例如,赋值、作用域规则和参数传递)所有 Python 对象类型都由 Python 语言以相同的方式处理,因此它们应该由单个 C 类型表示是恰当的。几乎所有 Python 对象都存在于堆上:您从不声明类型为 PyObject
的自动或静态变量,只能声明类型为 PyObject* 的指针变量。唯一的例外是类型对象;由于它们绝不应该被释放,因此它们通常是静态的 PyTypeObject
对象。
所有 Python 对象(甚至 Python 整数)都有一个类型和一个引用计数。对象的类型决定了它是什么类型的对象(例如,整数、列表或用户定义函数;标准类型层级结构中解释了更多)。对于每个众所周知的类型,都有一个宏来检查对象是否是该类型;例如,如果(且仅当)由 a 指向的对象是 Python 列表,则 PyList_Check(a)
为真。
引用计数¶
引用计数很重要,因为当今计算机的内存大小是有限的(而且通常受到严重限制);它计算有多少不同的地方对一个对象持有强引用。这样的地方可以是另一个对象,或者一个全局(或静态)C 变量,或者某个 C 函数中的局部变量。当对一个对象的最后一个强引用被释放时(即其引用计数变为零),该对象就会被解除分配。如果它包含对其他对象的引用,那些引用也会被释放。如果对这些其他对象不再有引用,它们也可能被解除分配,依此类推。(这里有一个明显的相互引用的对象问题;目前,解决方案是“不要那样做。”)
引用计数总是显式地操作。通常的方法是使用宏 Py_INCREF()
来获取一个对象的新引用(即将其引用计数增加一),以及 Py_DECREF()
来释放该引用(即将其引用计数减少一)。Py_DECREF()
宏比 incref 宏复杂得多,因为它必须检查引用计数是否变为零,然后导致调用对象的解分配器。解分配器是对象类型结构中包含的一个函数指针。类型特定的解分配器负责释放对象中包含的其他对象的引用(如果这是一个复合对象类型,例如列表),以及执行所需的任何其他最终化操作。引用计数不可能溢出;至少使用与虚拟内存中不同内存位置一样多的位来保存引用计数(假设 sizeof(Py_ssize_t) >= sizeof(void*)
)。因此,引用计数增加是一个简单的操作。
并非每个包含对象指针的局部变量都需要持有强引用(即增加引用计数)。理论上,当变量指向对象时,对象的引用计数会增加一;当变量超出作用域时,引用计数会减少一。然而,这两个操作会相互抵消,因此最终引用计数没有改变。使用引用计数的唯一真正原因是防止对象在我们的变量指向它期间被解除分配。如果我们知道至少有一个对对象的其他引用,并且其生命周期至少与我们的变量一样长,则不需要暂时获取新的强引用(即增加引用计数)。一个重要的情况是在通过扩展模块中的 C 函数作为参数传递给 Python 调用的对象;调用机制保证在调用期间持有对每个参数的引用。
然而,一个常见的陷阱是从列表中提取一个对象,并在不获取新引用的情况下持有它一段时间。其他一些操作可能会从列表中删除该对象,释放该引用,并可能解除分配它。真正的危险在于,看似无害的操作可能会调用任意 Python 代码,从而导致这种情况;存在一条代码路径允许控制从 Py_DECREF()
流回到用户,因此几乎任何操作都可能具有潜在危险。
一个安全的方法是始终使用通用操作(名称以 PyObject_
、PyNumber_
、PySequence_
或 PyMapping_
开头的函数)。这些操作总是会创建它们返回对象的新的强引用(即增加引用计数)。这使得调用者有责任在完成使用结果后调用 Py_DECREF()
;这很快就会成为第二天性。
引用计数详情¶
Python/C API 中函数的引用计数行为最好用引用的所有权来解释。所有权涉及引用,而不是对象(对象不被拥有:它们总是共享的)。“拥有引用”意味着当不再需要引用时,负责对其调用 Py_DECREF。所有权也可以转移,这意味着接收引用所有权的代码随后负责在不再需要时通过调用 Py_DECREF()
或 Py_XDECREF()
来最终释放它——或者将此责任传递(通常是给其调用者)。当函数将其引用所有权传递给其调用者时,称调用者收到一个新引用。当没有所有权转移时,称调用者借用该引用。对于借用引用,不需要做任何事情。
反之,当一个调用函数传入一个对象引用时,有两种可能性:函数偷取了对该对象的引用,或者它没有。_偷取引用_意味着当你将一个引用传递给一个函数时,该函数假定它现在拥有该引用,你不再对其负责。
很少有函数会偷取引用;两个显著的例外是 PyList_SetItem()
和 PyTuple_SetItem()
,它们会偷取对项的引用(但不是对项所放入的元组或列表的引用!)。这些函数被设计为偷取引用,因为它们常见于用新创建的对象填充元组或列表的惯用写法;例如,创建元组 (1, 2, "three")
的代码可能如下所示(暂时忽略错误处理;下面展示了更好的编写方式)
PyObject *t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(1L));
PyTuple_SetItem(t, 1, PyLong_FromLong(2L));
PyTuple_SetItem(t, 2, PyUnicode_FromString("three"));
在这里,PyLong_FromLong()
返回一个新引用,该引用立即被 PyTuple_SetItem()
偷走。当您想继续使用一个对象,尽管对其的引用将被偷走时,请在调用偷取引用的函数之前使用 Py_INCREF()
来获取另一个引用。
顺便说一句,PyTuple_SetItem()
是设置元组项的唯一方法;PySequence_SetItem()
和 PyObject_SetItem()
拒绝这样做,因为元组是不可变数据类型。您应该只对您自己创建的元组使用 PyTuple_SetItem()
。
使用 PyList_New()
和 PyList_SetItem()
可以编写用于填充列表的等效代码。
然而,在实践中,您很少会使用这些创建和填充元组或列表的方式。有一个通用函数 Py_BuildValue()
,它可以通过格式字符串从 C 值创建大多数常见对象。例如,上面两段代码可以替换为以下内容(它也处理了错误检查)
PyObject *tuple, *list;
tuple = Py_BuildValue("(iis)", 1, 2, "three");
list = Py_BuildValue("[iis]", 1, 2, "three");
更常见的情况是,将 PyObject_SetItem()
及相关函数用于您只是借用的项(例如传递给您正在编写的函数的参数)。在这种情况下,它们关于引用的行为更加合理,因为您不必为了放弃引用(“让它被偷走”)而获取新引用。例如,此函数将列表(实际上是任何可变序列)的所有项设置为给定项
int
set_all(PyObject *target, PyObject *item)
{
Py_ssize_t i, n;
n = PyObject_Length(target);
if (n < 0)
return -1;
for (i = 0; i < n; i++) {
PyObject *index = PyLong_FromSsize_t(i);
if (!index)
return -1;
if (PyObject_SetItem(target, index, item) < 0) {
Py_DECREF(index);
return -1;
}
Py_DECREF(index);
}
return 0;
}
函数返回值的情况略有不同。虽然将引用传递给大多数函数不会改变您对该引用的所有权责任,但许多返回对象引用的函数会赋予您对该引用的所有权。原因很简单:在许多情况下,返回的对象是即时创建的,您获得的引用是该对象的唯一引用。因此,返回对象引用的通用函数,如 PyObject_GetItem()
和 PySequence_GetItem()
,总是返回一个新引用(调用者成为该引用的所有者)。
重要的是要认识到,您是否拥有函数返回的引用取决于您调用的函数——羽毛(作为参数传递给函数的对象的类型)与此无关!因此,如果您使用 PyList_GetItem()
从列表中提取一个项,您不拥有该引用——但如果您使用 PySequence_GetItem()
从同一列表中获取同一项(该函数恰好接受完全相同的参数),您确实拥有对返回对象的引用。
这里有一个示例,说明如何编写一个函数来计算整数列表中各项的和;一个使用 PyList_GetItem()
,另一个使用 PySequence_GetItem()
。
long
sum_list(PyObject *list)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject *item;
n = PyList_Size(list);
if (n < 0)
return -1; /* Not a list */
for (i = 0; i < n; i++) {
item = PyList_GetItem(list, i); /* Can't fail */
if (!PyLong_Check(item)) continue; /* Skip non-integers */
value = PyLong_AsLong(item);
if (value == -1 && PyErr_Occurred())
/* Integer too big to fit in a C long, bail out */
return -1;
total += value;
}
return total;
}
long
sum_sequence(PyObject *sequence)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject *item;
n = PySequence_Length(sequence);
if (n < 0)
return -1; /* Has no length */
for (i = 0; i < n; i++) {
item = PySequence_GetItem(sequence, i);
if (item == NULL)
return -1; /* Not a sequence, or other failure */
if (PyLong_Check(item)) {
value = PyLong_AsLong(item);
Py_DECREF(item);
if (value == -1 && PyErr_Occurred())
/* Integer too big to fit in a C long, bail out */
return -1;
total += value;
}
else {
Py_DECREF(item); /* Discard reference ownership */
}
}
return total;
}
类型¶
Python/C API 中还有少数其他数据类型扮演着重要角色;大多数是简单的 C 类型,例如 int、long、double 和 char*。少数结构类型用于描述用于列出模块导出的函数或新对象类型的数据属性的静态表,另一种用于描述复数的值。这些将在讨论使用它们的函数时一并讨论。
-
type Py_ssize_t¶
- 作为 稳定 ABI 的一部分。
一个有符号整数类型,其中
sizeof(Py_ssize_t) == sizeof(size_t)
。C99 没有直接定义这样的类型 (size_t 是一个无符号整数类型)。有关详细信息,请参阅 PEP 353。PY_SSIZE_T_MAX
是Py_ssize_t
类型的最大正值。
异常¶
Python 程序员只需要在需要特定错误处理时处理异常;未处理的异常会自动传播给调用者,然后传播给调用者的调用者,依此类推,直到它们到达顶层解释器,在那里它们会连同堆栈回溯一起报告给用户。
然而,对于 C 程序员来说,错误检查总是必须显式的。Python/C API 中的所有函数都可能引发异常,除非函数文档中另有明确说明。通常,当函数遇到错误时,它会设置一个异常,丢弃其拥有的任何对象引用,并返回一个错误指示符。如果未另行说明,此指示符要么是 NULL
,要么是 -1
,具体取决于函数的返回类型。少数函数返回布尔真/假结果,其中假表示错误。极少数函数不返回明确的错误指示符或具有模糊的返回值,并且需要使用 PyErr_Occurred()
进行显式错误测试。这些异常总是明确记录的。
异常状态保存在每个线程的存储中(这相当于在非线程应用程序中使用全局存储)。一个线程可以处于两种状态之一:发生异常,或者没有发生异常。PyErr_Occurred()
函数可用于检查此情况:当发生异常时,它返回一个借用异常类型对象的引用,否则返回 NULL
。有许多函数可以设置异常状态:PyErr_SetString()
是最常见的(尽管不是最通用的)设置异常状态的函数,而 PyErr_Clear()
清除异常状态。
完整的异常状态由三个对象组成(所有这些对象都可以是 NULL
):异常类型、相应的异常值和回溯。它们与 sys.exc_info()
的 Python 结果具有相同的含义;但是,它们并不相同:Python 对象表示 Python try
… except
语句正在处理的最后一个异常,而 C 级异常状态仅在异常在 C 函数之间传递时存在,直到它到达 Python 字节码解释器的主循环,该主循环负责将其传输到 sys.exc_info()
等。
请注意,从 Python 1.5 开始,从 Python 代码访问异常状态的首选、线程安全方式是调用函数 sys.exc_info()
,它返回 Python 代码的每线程异常状态。此外,两种访问异常状态的方式的语义都已更改,以便捕获异常的函数将保存并恢复其线程的异常状态,以保留其调用者的异常状态。这可以防止由看似无害的函数覆盖正在处理的异常而导致的常见异常处理错误;它还减少了对回溯中堆栈帧引用的对象的通常不必要的生命周期延长。
一般来说,调用另一个函数执行某项任务的函数应该检查被调用函数是否引发了异常,如果是,则将异常状态传递给其调用者。它应该丢弃其拥有的任何对象引用,并返回一个错误指示符,但它不应该设置另一个异常——那会覆盖刚刚引发的异常,并丢失有关错误确切原因的重要信息。
上面 sum_sequence()
示例中展示了一个检测和传递异常的简单示例。恰好此示例在检测到错误时不需要清理任何拥有的引用。以下示例函数展示了一些错误清理。首先,为了提醒您为什么喜欢 Python,我们展示了等效的 Python 代码
def incr_item(dict, key):
try:
item = dict[key]
except KeyError:
item = 0
dict[key] = item + 1
这是相应的 C 代码,所有细节都一览无余
int
incr_item(PyObject *dict, PyObject *key)
{
/* Objects all initialized to NULL for Py_XDECREF */
PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL;
int rv = -1; /* Return value initialized to -1 (failure) */
item = PyObject_GetItem(dict, key);
if (item == NULL) {
/* Handle KeyError only: */
if (!PyErr_ExceptionMatches(PyExc_KeyError))
goto error;
/* Clear the error and use zero: */
PyErr_Clear();
item = PyLong_FromLong(0L);
if (item == NULL)
goto error;
}
const_one = PyLong_FromLong(1L);
if (const_one == NULL)
goto error;
incremented_item = PyNumber_Add(item, const_one);
if (incremented_item == NULL)
goto error;
if (PyObject_SetItem(dict, key, incremented_item) < 0)
goto error;
rv = 0; /* Success */
/* Continue with cleanup code */
error:
/* Cleanup code, shared by success and failure path */
/* Use Py_XDECREF() to ignore NULL references */
Py_XDECREF(item);
Py_XDECREF(const_one);
Py_XDECREF(incremented_item);
return rv; /* -1 for error, 0 for success */
}
这个例子代表了 C 语言中 goto
语句的一种受认可的用法!它说明了使用 PyErr_ExceptionMatches()
和 PyErr_Clear()
来处理特定异常,以及使用 Py_XDECREF()
来处理可能为 NULL
的自有引用(注意名称中的 'X'
;Py_DECREF()
在遇到 NULL
引用时会崩溃)。重要的是,用于保存自有引用的变量必须初始化为 NULL
才能正常工作;同样,建议的返回值初始化为 -1
(失败),并且仅在最终调用成功后才设置为成功。
嵌入 Python¶
Python 解释器的嵌入者(与扩展模块编写者不同)唯一需要关注的重要任务是 Python 解释器的初始化,以及可能的最终化。解释器的大多数功能只有在解释器初始化后才能使用。
基本的初始化函数是 Py_Initialize()
。它初始化已加载模块的表,并创建基本模块 builtins
、__main__
和 sys
。它还初始化模块搜索路径 (sys.path
)。
Py_Initialize()
不会设置“脚本参数列表”(sys.argv
)。如果以后执行的 Python 代码需要此变量,则必须设置 PyConfig.argv
和 PyConfig.parse_argv
:请参阅Python 初始化配置。
在大多数系统上(特别是 Unix 和 Windows,尽管细节略有不同),Py_Initialize()
根据其对标准 Python 解释器可执行文件位置的最佳猜测来计算模块搜索路径,假设 Python 库相对于 Python 解释器可执行文件位于固定位置。特别是,它会在 shell 命令搜索路径(环境变量 PATH
)中查找名为 python
的可执行文件所在父目录下的名为 lib/pythonX.Y
的目录。
例如,如果 Python 可执行文件在 /usr/local/bin/python
中找到,它会假定库在 /usr/local/lib/pythonX.Y
中。(实际上,此特定路径也是“回退”位置,当在 PATH
中找不到名为 python
的可执行文件时使用。)用户可以通过设置环境变量 PYTHONHOME
来覆盖此行为,或者通过设置 PYTHONPATH
在标准路径之前插入其他目录。
嵌入式应用程序可以通过在调用 Py_InitializeFromConfig()
之前设置 PyConfig.program_name
来引导搜索。请注意,PYTHONHOME
仍会覆盖此设置,并且 PYTHONPATH
仍会插入到标准路径之前。需要完全控制的应用程序必须提供自己的 Py_GetPath()
、Py_GetPrefix()
、Py_GetExecPrefix()
和 Py_GetProgramFullPath()
实现(所有这些都在 Modules/getpath.c
中定义)。
有时,希望“反初始化”Python。例如,应用程序可能想重新开始(再次调用 Py_Initialize()
),或者应用程序只是完成了对 Python 的使用并希望释放 Python 分配的内存。这可以通过调用 Py_FinalizeEx()
来实现。Py_IsInitialized()
函数返回 Python 当前是否处于初始化状态。有关这些函数的更多信息将在后面的章节中给出。请注意,Py_FinalizeEx()
不会释放 Python 解释器分配的所有内存,例如,扩展模块分配的内存目前无法释放。
调试构建¶
Python 可以使用多个宏构建,以启用对解释器和扩展模块的额外检查。这些检查往往会增加大量的运行时开销,因此默认情况下不启用。
有关各种调试构建的完整列表,请参见 Python 源代码分发中的 Misc/SpecialBuilds.txt
文件。有支持引用计数跟踪、调试内存分配器或主解释器循环低级分析的构建。本节的其余部分将仅描述最常用的构建。
-
Py_DEBUG¶
使用 Py_DEBUG
宏编译解释器会产生通常所说的Python 的调试构建。在 Unix 构建中,通过将 --with-pydebug
添加到 ./configure
命令来启用 Py_DEBUG
。它也隐含在非 Python 特定宏 _DEBUG
的存在中。当在 Unix 构建中启用 Py_DEBUG
时,编译器优化被禁用。
除了下面描述的引用计数调试之外,还执行额外的检查,请参阅Python 调试构建。
定义 Py_TRACE_REFS
可启用引用跟踪(请参阅 configure --with-trace-refs 选项
)。定义后,通过为每个 PyObject
添加两个额外字段来维护活动对象的双向循环链表。总分配量也会被跟踪。退出时,会打印所有现有引用。(在交互模式下,这在解释器运行的每个语句之后发生。)
有关更详细的信息,请参阅 Python 源代码分发中的 Misc/SpecialBuilds.txt
。
推荐的第三方工具¶
以下第三方工具提供了更简单和更复杂的创建 Python C、C++ 和 Rust 扩展的方法
使用此类工具可以帮助避免编写与特定 CPython 版本紧密绑定的代码,避免引用计数错误,并更多地关注您自己的代码而不是使用 CPython API。通常,可以通过更新工具来支持新版本的 Python,并且您的代码通常会自动使用更新和更高效的 API。一些工具还支持从一组源编译用于其他 Python 实现。
这些项目不受维护 Python 的人员支持,问题需要直接向这些项目提出。请记住检查项目是否仍在维护和支持,因为上面的列表可能会过时。
参见
- Python 打包用户指南:二进制扩展
Python 打包用户指南不仅涵盖了几种可用于简化二进制扩展创建的工具,还讨论了最初创建扩展模块的各种原因。