简介¶
Python 的应用程序编程接口 (API) 为 C 和 C++ 程序员提供了在各种级别访问 Python 解释器的能力。该 API 同样适用于 C++,但为了简洁起见,通常称为 Python/C API。使用 Python/C API 有两个根本不同的原因。第一个原因是编写用于特定目的的扩展模块;这些是扩展 Python 解释器的 C 模块。这可能是最常见的用法。第二个原因是将 Python 用作更大应用程序中的组件;这种技术通常被称为在应用程序中嵌入 Python。
编写扩展模块是一个相对容易理解的过程,其中“菜谱”方法非常有效。有一些工具可以在一定程度上自动化该过程。虽然人们自早期就将 Python 嵌入到其他应用程序中,但嵌入 Python 的过程不如编写扩展模块那么直接。
许多 API 函数在嵌入或扩展 Python 时都很有用;此外,大多数嵌入 Python 的应用程序也需要提供自定义扩展,因此最好在尝试在实际应用程序中嵌入 Python 之前熟悉如何编写扩展。
编码标准¶
如果你正在编写 C 代码以包含在 CPython 中,你必须遵循 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 版本可能会定义以这些前缀之一开头的其他名称。
头文件通常与 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¶
声明一个扩展模块
PyInit
初始化函数。该函数的返回类型是 PyObject*。该宏声明平台所需的任何特殊链接声明,并为 C++ 将该函数声明为extern "C"
。初始化函数必须命名为
PyInit_name
,其中 name 是模块的名称,并且应该是模块文件中定义的唯一非static
项。示例static struct PyModuleDef spam_module = { PyModuleDef_HEAD_INIT, .m_name = "spam", ... }; PyMODINIT_FUNC PyInit_spam(void) { return PyModule_Create(&spam_module); }
-
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
(请参阅PyConfig.use_environment
),则返回NULL
。
-
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 时没有文档字符串,则该值将为空。按照 PEP 7 中的规定,为支持在没有文档字符串的情况下构建 Python,请使用
PyDoc_STRVAR
来处理文档字符串。示例
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()
来获取对对象的新引用(即将其引用计数增加 1),并使用 Py_DECREF()
来释放该引用(即将其引用计数减 1)。Py_DECREF()
宏比 incref 宏复杂得多,因为它必须检查引用计数是否变为零,然后导致调用对象的释放器。释放器是包含在对象类型结构中的函数指针。特定于类型的释放器负责释放对象中包含的其他对象的引用(如果这是一个复合对象类型,例如列表),并执行所需的任何其他最终化操作。引用计数不可能溢出;至少使用与虚拟内存中不同内存位置一样多的位来保存引用计数(假设 sizeof(Py_ssize_t) >= sizeof(void*)
)。因此,引用计数递增是一个简单的操作。
对于每个包含指向对象的指针的局部变量,不必持有 强引用(即增加引用计数)。理论上,当变量指向对象时,对象的引用计数会增加 1,而当变量超出作用域时,对象的引用计数会减少 1。但是,这两者相互抵消,因此最终引用计数不会改变。使用引用计数的唯一真正原因是防止对象在我们变量指向它时被释放。如果我们知道至少还有另一个对该对象的引用与我们的变量一样长,则无需临时获取新的 强引用(即增加引用计数)。出现这种情况的一个重要场景是,在从 Python 调用的扩展模块中作为参数传递给 C 函数的对象;调用机制保证在调用期间保持对每个参数的引用。
但是,一个常见的陷阱是从列表中提取一个对象并在不获取新引用的情况下保留它一段时间。一些其他操作可能会将对象从列表中删除,释放该引用,并可能释放它。真正的危险是,看起来无辜的操作可能会调用任意的 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
,具体取决于函数的返回类型。一些函数返回布尔值 true/false 结果,其中 false 表示错误。极少数函数不返回显式错误指示符或具有不明确的返回值,并且需要使用 PyErr_Occurred()
进行显式错误测试。这些例外情况始终会明确记录。
异常状态在每个线程的存储中维护(这相当于在非线程应用程序中使用全局存储)。线程可以处于两种状态之一:已发生异常或未发生异常。函数 PyErr_Occurred()
可用于检查这一点:当发生异常时,它返回对异常类型对象的借用引用,否则返回 NULL
。有许多函数可以设置异常状态:PyErr_SetString()
是设置异常状态最常见(但不是最通用)的函数,而 PyErr_Clear()
清除异常状态。
完整的异常状态由三个对象组成(所有对象都可以是 NULL
):异常类型、相应的异常值和回溯。它们的含义与 Python 的 sys.exc_info()
的结果相同;但是,它们并不相同: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'
;当遇到 NULL
引用时,Py_DECREF()
会崩溃)。重要的是,用于保存拥有的引用的变量要初始化为 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()
来完成。如果 Python 当前处于初始化状态,则函数 Py_IsInitialized()
返回 true。有关这些函数的更多信息将在后面的章节中给出。请注意,Py_FinalizeEx()
不会释放 Python 解释器分配的所有内存,例如,扩展模块分配的内存当前无法释放。
调试版本¶
可以使用多个宏构建 Python,以启用对解释器和扩展模块的额外检查。这些检查往往会给运行时增加大量的开销,因此默认情况下不启用它们。
Python 源代码分发中的文件 Misc/SpecialBuilds.txt
中提供了各种类型的调试版本的完整列表。可以使用支持跟踪引用计数、调试内存分配器或主解释器循环的低级分析的版本。本节的其余部分将仅描述最常用的版本。
-
Py_DEBUG¶
使用定义了 Py_DEBUG
宏的解释器进行编译,会产生通常所说的 Python 的调试版本。Py_DEBUG
在 Unix 构建中通过将 --with-pydebug
添加到 ./configure
命令中启用。它也隐含于非 Python 特定的 _DEBUG
宏的存在。当在 Unix 构建中启用 Py_DEBUG
时,编译器优化将被禁用。
除了下面描述的引用计数调试之外,还会执行额外的检查,请参阅 Python 调试版本。
定义 Py_TRACE_REFS
会启用引用跟踪(请参阅 configure --with-trace-refs 选项
)。当定义时,通过向每个 PyObject
添加两个额外字段,来维护活动对象的循环双向链表。还会跟踪总分配量。退出时,将打印所有现有的引用。(在交互模式下,这会在解释器运行的每个语句之后发生。)
有关更详细的信息,请参阅 Python 源代码发行版中的 Misc/SpecialBuilds.txt
。