简介¶
Python 的应用程序编程接口 (API) 为 C 和 C++ 程序员提供了在不同级别访问 Python 解释器的途径。该 API 同样适用于 C++,但为了简洁起见,通常称为 Python/C API。使用 Python/C API 主要有两个根本不同的原因。第一个原因是为特定目的编写扩展模块;这些是扩展 Python 解释器的 C 模块。这可能是最常见的用途。第二个原因是在更大的应用程序中使用 Python;这种技术通常被称为嵌入 Python 到应用程序中。
编写扩展模块是一个相对容易理解的过程,其中“食谱”方法非常有效。有一些工具在一定程度上自动化了这个过程。虽然人们从 Python 诞生之初就将其嵌入到其他应用程序中,但嵌入 Python 的过程并不像编写扩展那样简单。
许多 API 函数在您嵌入或扩展 Python 时都很有用;此外,大多数嵌入 Python 的应用程序都需要提供自定义扩展,因此在尝试将 Python 嵌入到实际应用程序中之前,熟悉编写扩展可能是一个好主意。
编码规范¶
如果您正在编写要包含在 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¶
声明一个扩展模块
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] 中的整数。此宏返回
c
转换为unsigned char
。
-
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()¶
当您有一个设计上无法到达的代码路径时使用它。例如,在
default:
子句中,在switch
语句中,所有可能的值都在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 整数)都具有 类型 和 引用计数。对象的类型决定了它是什么类型的对象(例如,整数、列表或用户定义的函数;如 标准类型层次结构 中所述,还有更多类型)。对于每个众所周知的类型,都有一个宏来检查对象是否为该类型;例如,PyList_Check(a)
为真(当且仅当)a 指向的对象是 Python 列表。
引用计数¶
引用计数很重要,因为当今的计算机具有有限的(而且通常非常有限的)内存大小;它计算有多少个不同的地方对一个对象具有 强引用。这样的地方可能是另一个对象,或者是一个全局(或静态)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
,具体取决于函数的返回类型。一些函数返回布尔真/假结果,其中假表示错误。很少有函数不返回显式错误指示符或返回值不明确,需要使用 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'
;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_Initialize()
之前 调用 Py_SetProgramName(file)
来引导搜索。请注意,PYTHONHOME
仍然会覆盖此设置,并且 PYTHONPATH
仍然会插入到标准路径的前面。需要完全控制的应用程序必须提供自己的 Py_GetPath()
、Py_GetPrefix()
、Py_GetExecPrefix()
和 Py_GetProgramFullPath()
实现(所有这些都在 Modules/getpath.c
中定义)。
有时,需要“取消初始化” Python。例如,应用程序可能想要重新开始(再次调用 Py_Initialize()
)或者应用程序只是完成了对 Python 的使用,并希望释放 Python 分配的内存。这可以通过调用 Py_FinalizeEx()
来实现。函数 Py_IsInitialized()
在 Python 当前处于初始化状态时返回 true。这些函数的更多信息将在后面的章节中介绍。请注意,Py_FinalizeEx()
不会 释放 Python 解释器分配的所有内存,例如,当前无法释放扩展模块分配的内存。
调试版本¶
Python 可以使用多个宏来构建,以启用对解释器和扩展模块的额外检查。这些检查往往会给运行时增加大量的开销,因此默认情况下不会启用它们。
各种调试版本的完整列表位于 Python 源代码分发包中的 Misc/SpecialBuilds.txt
文件中。可用的版本支持跟踪引用计数、调试内存分配器或对主解释器循环进行低级分析。在本节的剩余部分中,只介绍最常用的版本。
-
Py_DEBUG¶
如果在编译解释器时定义了 Py_DEBUG
宏,则会生成通常所说的 Python 调试版本。在 Unix 构建中,可以通过在 ./configure
命令中添加 --with-pydebug
来启用 Py_DEBUG
。它也隐含于非 Python 特定的 _DEBUG
宏的存在。当在 Unix 构建中启用 Py_DEBUG
时,编译器优化会被禁用。
除了下面描述的引用计数调试之外,还会执行额外的检查,请参见 Python 调试版本。
定义 Py_TRACE_REFS
会启用引用跟踪(参见 configure --with-trace-refs option
)。当定义时,会通过在每个 PyObject
中添加两个额外的字段来维护一个活动对象的循环双向链表。总分配也会被跟踪。退出时,会打印所有现有的引用。(在交互模式下,这会在解释器运行的每个语句之后发生。)
请参阅 Python 源代码分发包中的 Misc/SpecialBuilds.txt
以获取更多详细信息。