• 第一章 概述


    1.1 代码标准

    如果你想要编写可包含于 CPython 的 C 代码,你 必须遵循在 PEP 7 中定义的指导原则和标准。这些指导原则适用于任何你所要扩展的 Python 版本。在编写你自己的第三方扩展模块时可以不必遵循这些规范,除非你准备在日后向 Python 贡献这些模块。

    1.2 包含文件

    使用 Python/C API 所需要的全部函数、类型和宏定义可通过下面这行语句包含到你的代码之中:

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    
    • 1
    • 2

    这意味着包含以下标准头文件:<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”,因此 API 在 C++ 中使用此 API 不必再做任何特殊处理。

    1.3 有用的宏

    Python 头文件中定义了一些有用的宏。许多是在靠近它们被使用的地方定义的(例如Py_RETURN_NONE)。其他更为通用的则定义在这里。这里所显示的并不是一个完整的列表。

    • 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; }
    
    • 1
    • Py_CHARMASK©
      参数必须为 [-128, 127] 或 [0, 255] 范围内的字符或整数类型。这个宏将 c 强制转换为 unsigned char返回。
    • Py_DEPRECATED(version)
      弃用声明。该宏必须放置在符号名称前。
      示例:
    Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);
    
    • 1

    在 3.8 版更改: 添加了 MSVC 支持。

    • Py_GETENV(s)
      与 getenv(s) 类 似, 但 是 如 果 命 令 行 上 传 递 了 -E , 则 返 回 NULL (即 如 果 设 置 了Py_IgnoreEnvironmentFlag )。
    • 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; }
    
    • 1

    3.11 新版功能.

    • Py_STRINGIFY(x)
      将 x 转换为 C 字符串。例如 Py_STRINGIFY(123) 返回 “123”。
      3.4 新版功能.
    • Py_UNREACHABLE()
      这个可以在你有一个设计上无法到达的代码路径时使用。例如,当一个 switch 语句中所有可能的值都已被 case 子句覆盖了,就可将其用在 default: 子句中。当你非常想在某个位置放一个 assert(0)或 abort() 调用时也可以用这个。
      在 release 模式下,该宏帮助编译器优化代码,并避免发出不可到达代码的警告。例如,在 GCC 的 release模式下,该宏使用 __builtin_unreachable() 实现。
      Py_UNREACHABLE() 的一个用法是调用一个不会返回,但却没有声明_Py_NO_RETURN 的函数之后。
      如果一个代码路径不太可能是正常代码,但在特殊情况下可以到达,就不能使用该宏。例如,在低内存条件下,或者一个系统调用返回超出预期范围值,诸如此类,最好将错误报告给调用者。如果无法将错误报告给调用者,可以使用Py_FatalError() 。
      3.7 新版功能
    • Py_UNUSED(arg)
      用 于 函 数 定 义 中 未 使 用 的 参 数, 从 而 消 除 编 译 器 警 告。 例 如:int func(int a, intPy_UNUSED(b)) { return a; } 。
      3.4 新版功能.
    • PyDoc_STRVAR(name, str)
      创建一个可以在文档字符串中使用的,名字为 name 的变量。如果不和文档字符串一起构建 Python,该值将为空。
      如 PEP 7 所述,使用PyDoc_STRVAR 作为文档字符串,以支持不和文档字符串一起构建 Python 的情况。
      示例:
    PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element.");
    static PyMethodDef deque_methods[] = {
    // ...
    {"pop", (PyCFunction)deque_pop, METH_NOARGS, pop_doc},
    // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • PyDoc_STR(str)
      为给定的字符串输入创建一个文档字符串,或者当文档字符串被禁用时,创建一个空字符串。
      如 PEP 7 所述,使用PyDoc_STR 指定文档字符串,以支持不和文档字符串一起构建 Python 的情况。
      示例:
    static PyMethodDef pysqlite_row_methods[] = { {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS,
    PyDoc_STR("Returns the keys of the row.")},
    {NULL, NULL}
    };
    
    • 1
    • 2
    • 3
    • 4

    1.4 对象、类型和引用计数

    多数 Python/C API 有一个或多个参数,以及一个 PyObject* 类型的返回值。这种类型是指向任意 Python 对象的不透明数据类型的指针。所有 Python 对象类型在大多数情况下都被 Python 语言由相同的方式处理(例如,赋值,作用域规则,和参数传递),因此将它们由单个 C 类型表示才合适。几乎所有 Python 对象存放在堆中:你不能声明一个类型为PyObject 的自动或静态的变量,只能声明类型为 PyObject* 的指针。type 对象是唯
    一的例外,因为它们永远不能被释放,所以它们通常是静态的PyTypeObject 对象。
    所有 Python 对象(甚至 Python 整数)都有一个 type 和一个 reference count。对象的类型确定它是什么类型的对象(例如整数、列表或用户定义函数;还有更多,如 types 中所述)。对于每个众所周知的类型,都有一个宏来检查对象是否属于该类型;例如,当(且仅当)a 所指的对象是 Python 列表时 PyList_Check(a) 为真。

    1.4.1 引用计数

    引用计数非常重要,因为现代计算机内存(通常十分)有限;它计算有多少不同的地方引用同一个对象。这样的地方可以是某个对象,或者是某个全局(或静态)C 变量,亦或是某个 C 函数的局部变量。当一个对象的引用计数变为 0,释放该对象。如果这个已释放的对象包含其它对象的引用计数,则递减这些对象的引用计数。如果这些对象的引用计数减少为零,则可以依次释放这些对象,依此类推。(这里有一个很明显的问题——对象之间相互引用;目前,解决方案是“不要那样做”。)
    总是显式操作引用计数。通常的方法是使用宏Py_INCREF() 来增加一个对象的引用计数,使用宏Py_DECREF() 来减少一个对象的引用计数。宏Py_DECREF() 必须检查引用计数是否为零,然后调用对象的释放器,因此它比 incref 宏复杂得多。释放器是一个包含在对象类型结构中的函数指针。如果对象是复合对象类型(例如列表),则类型特定的释放器负责递减包含在对象中的其他对象的引用计数,并执行所需的终结。引用计数不会溢出,至少用与虚拟内存中不同内存位置一样多的位用于保存引用计数(即sizeof(Py_ssize_t) >= sizeof(void*) )。因此,引用计数递增是一个简单的操作。
    没有必要为每个包含指向对象的指针的局部变量增加对象的引用计数。理论上,当变量指向对象时,对象的引用计数增加 1 ,当变量超出范围时,对象的引用计数减少 1 。但是,这两者相互抵消,所以最后引用计数没有改变。使用引用计数的唯一真正原因是只要我们的变量指向它,就可以防止对象被释放。如果知道至少有一个对该对象的其他引用存活时间至少和我们的变量一样长,则没必要临时增加引用计数。一个典型的情形是,对象作为参数从 Python 中传递给被调用的扩展模块中的 C 函数时,调用机制会保证在调用期间持有对所有参数的引用。
    但是,有一个常见的陷阱是从列表中提取一个对象,并将其持有一段时间,而不增加其引用计数。某些操作可能会从列表中删除某个对象,减少其引用计数,并有可能重新分配这个对象。真正的危险是,这个看似无害的操作可能会调用任意 Python 代码——也许有一个代码路径允许控制流从Py_DECREF() 回到用户,因此在复合对象上的操作都存在潜在的风险。
    一个安全的方式是始终使用泛型操作(名称以 PyObject_ ,PyNumber_ ,PySequence_ 或 PyMapping_开头的函数)。这些操作总是增加它们返回的对象的引用计数。这让调用者有责任在获得结果后调用Py_DECREF() 。习惯这种方式很简单。
    引用计数细节
    Python/CAPI中的函数的引用计数行为最好用引用的所有权来解释。所有权属于引用,从不属于对象(对象不属于所有:它们总是共享的)。“拥有一个引用”意味着在不再需要引用时负责对其调用Py_DECREF。所有权也可以转移,这意味着接收引用所有权的代码将通过在不再需要时调用Py_DECREF()或Py_XDECREF()来最终减少它——或者传递此责任(通常传递给调用者)。当函数将引用的所有权传递给其调用者时,调用者将接收一个新的引用。当没有所有权被转移时,调用者被称为借用引用。不需要做任何借来的参考。
    相反,当调用函数传递到对对象的引用时,有两种可能性:函数窃取对对象的引用,或者它没有。窃取一个引用意味着,当您传递一个引用到一个函数时,该函数假定它现在拥有该引用,而您不再对它负责。
    很少有函数窃取引用;两个值得注意的例外是PyList_SetItem()和PyTuple_SetItem(),它们窃取对项的引用(但不是包含项的元组或列表!)。这些函数被设计用来窃取引用,因为有一个常见的习惯用法,用于用新创建的对象填充元组或列表;例如,用于创建元组的代码(1,2,“3”)可能是这样的(暂时忘记了错误处理;更好的编码方法如下所示):

    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"));
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里,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");
    
    • 1
    • 2
    • 3

    更常见的情况是使用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; }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    对于函数返回值的情况略有不同。虽然将引用传递给大多数函数不会更改该引用的所有权责任,但对于返回对象引用的许多函数将赋予您引用的所有权。原因很简单:在许多情况下,返回的对象是动态创建的,而您得到的引用是对该对象的唯一引用。因此,返回对象引用的泛型函数,如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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    1.4.2 类型

    在Python/CAPI中,几乎没有其他数据类型发挥重要作用;大多数都是简单的C类型,如int、长、双和char*。几种结构类型用于描述静态表,用于列出模块导出的函数或新对象类型的数据属性,另一种结构类型用于描述复数的值。这些问题将与使用它们的函数一起讨论。

    1.5 异常

    Python 程序员只需要处理特定需要处理的错误异常;未处理的异常会自动传递给调用者,然后传递给调用者
    的调用者,依此类推,直到他们到达顶级解释器,在那里将它们报告给用户并伴随堆栈回溯。
    然而,对于C程序员来说,错误检查总是必须是显式的。Python/CAPI中的所有函数都可以引发异常,除非在函数的文档中另有明确的声明。通常,当一个函数遇到一个错误时,它会设置一个异常,丢弃它所拥有的任何对象引用,并返回一个错误指示器。如果没有其他记录,此指示器是NULL或-1,这取决于函数的返回类型。一些函数返回布尔真/假结果,false表示错误。很少有函数不返回一个显式的错误指示器或有一个不明确的返回值,并且需要使用PyErr_Occurred()显式地测试错误。这些例外情况总是被明确地记录下来。
    在每个线程存储中保持异常状态(这相当于在非线程应用程序中使用全局存储)。线程可以处于两种状态之一:已发生异常,或未发生异常。函数PyErr_Occurred()可用于检查:当发生异常时,它返回对异常类型对象的借用引用,否则返回NULL。有许多函数可以设置异常状态:PyErr_SetString()是设置异常状态的最常见的(虽然不是最通用的)函数,而PyErr_Clear()会清除异常状态。
    完全异常状态由三个对象(均可为NULL)组成:异常类型、相应的异常值和回溯。它们的含义与sys.exc_info()的Python结果相同;但是,它们并不相同:Python对象表示由Python尝试处理的最后一个异常……除了语句,而C级异常状态只在C函数之间传递异常时存在,直到它到达Python字节码解释器的主循环,该循环负责将其传输给sys.exc_info()和朋友。
    注意,从Python1.5开始,从Python代码中访问异常状态的首选线程安全方法是调用函数sys.exc_info(),它返回Python代码的每个线程异常状态。此外,访问异常状态的两种方式的语义都发生了变化,因此捕获异常的函数将保存并恢复其线程的异常状态,以保存其调用者的异常状态。这可以防止异常处理代码中常见的错误,因为函数覆盖正在处理的异常;它还减少了回溯中堆栈帧引用的对象通常不需要的生命周期扩展。作为一般原则,一个调用另一个函数来执行某个任务的函数应该检查被调用的函数是否引发了异常,如果是,则将异常状态传递给它的调用者。它应该丢弃它所拥有的任何对象引用,并返回一个错误指示器,但它不应该设置另一个异常——这将覆盖刚刚引发的异常,并丢失关于错误的确切原因的重要信息。
    上面的sum_serem()示例显示了一个检测异常并传递它们的简单示例。所以,恰好这个示例在检测到错误时不需要清理任何已拥有的引用。下面的示例函数显示了一些错误清理。首先,为了提醒您为什么喜欢Python,我们展示了等效的Python代码:

    def incr_item(dict, key):
    try:
    item = dict[key]
    except KeyError:
    item = 0
    dict[key] = item + 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    下面是对应的闪耀荣光的 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 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    这个例子表示了在C!它说明了使用PyErr_ExceptionMatches()和PyErr_Clear()来处理特定的异常,以及使用Py_XDECREF()来处理可能为NULL的所有引用(注意名称中的“X”;当遇到NULL引用时,Py_DECREF()会崩溃)。重要的是,将用于保存拥有引用的变量初始化为NULL才能工作;同样,建议的返回值初始化为-1(失败),并且只有在最终调用成功后才设置为成功。

    1.6 嵌入 Python

    只有Python解释器的嵌入器(相对于扩展编写器)需要担心的一个重要任务是Python解释器的初始化,甚至可能是最终化。解释器的大多数功能只能在初始化解释器之后才能使用。基本的初始化函数是Py_Initialize()。这将初始化加载模块的表,并创建基本模块内置、__主__和sys。它还初始化了模块搜索路径(sys.path)。
    Py_Initialize()没有设置“脚本参数列表”(sys.argv)。如果稍后将执行的Python代码需要这个变量,则必须设置PyConfig.argv和PyConfig.parse_argv:请参见Python初始化配置。
    在大多数系统(特别是在Unix和窗口,尽管细节略有不同),Py_Initialize()计算模块搜索路径基于其最佳猜测的标准Python解释器可执行文件的位置,假设Python库被发现在一个固定的位置相对于Python解释器可执行文件。特别是,它寻找一个名为lib/pythonX.Y的目录。相对于父目录,在shellpython命令搜索路径(环境变量路径)上找到可执行文件的目录。
    例如,如果在/usr/local/bin/python中找到Python可执行文件,它将假设库在/usr/local/lib/pythonX.Y中。(实际上,这个特定的路径也是“回退”位置,当在路径中没有找到名为python的可执行文件时使用。)用户可以通过设置环境变量双通路径来覆盖此行为,或者通过设置双通路径来在标准路径前面插入其他目录。
    嵌入应用程序可以在调用Py_Initialize()之前通过调用Py_SetProgramName(文件)来引导搜索。注意,必通家仍然覆盖这个,必通路径仍然插入在标准路径的前面。一个需要完全控制的应用程序必须提供它自己的Py_GetPath()、Py_GetPrefix()、Py_GetExecPrefix()和Py_GetProgramFullPath()的实现(都在模块/获取路径.c中定义)。
    有时,最好是“取消初始化”Python。例如,应用程序可能希望重新开始(对Py_Initialize()进行另一次调用),或者应用程序只是使用Python来完成,并希望释放由Python分配的内存。这可以通过调用Py_FinalizeEx()来实现。如果Python当前处于初始化状态,则函数Py_IsInitialized()返回true。有关这些功能的更多信息将在后面的章节中介绍。请注意,Py_FinalizeEx()并没有释放由Python解释器分配的所有内存,例如,当前不能释放由扩展模块分配的内存。

    1.7 调试构建

    Python可以使用多个宏来构建,以启用对解释器和扩展模块的额外检查。这些检查往往会给运行时增加大量的开销,因此在默认情况下不会启用它们。
    各种类型的调试构建的完整列表在Python源代码发行版本的文件Misc/SpecialBuilds.txt中。这些构建支持跟踪引用计数、调试内存分配器或对主解释器循环的底层分析。本节的其余部分将只描述最常用的构建。
    使用定义的Py_DEBUG宏编译解释器会产生通常称为Python的调试构建。Py_DEBUG在Unix构建中添加来启用。非python特定的_DEBUG宏的存在也暗示了这一点。当在Unix构建中启用了Py_DEBUG时,编译器优化将被禁用。
    除了下面描述的引用计数调试外,还需要执行额外的检查,请参见Python调试构建。定义Py_TRACE_REFS启用引用跟踪(请参阅配置-跟踪参考选项)。定义后,将通过向每个PyObject添加两个额外的字段来维护活动对象的循环双链接列表。总分配也被跟踪。在退出时,将打印所有现有的引用。(在交互模式下,这发生在解释器运行的每个语句之后。)
    有关更多详细信息,请参阅 Python 源代码中的 Misc/SpecialBuilds.txt 。

  • 相关阅读:
    能ping通,TCP就一定能连通吗?
    【实践】一种基于Spring Boot最简单的RESTFul接口版本管理
    【EI会议征稿】第二届纯数学、应用数学与计算数学国际学术会议(PACM 2024)
    开发制作小程序时要注意什么?
    AC修炼计划(AtCoder Regular Contest 162)
    Linux命令行教程:使用head和tail命令快速查看文件的开头和结尾
    支持JDK19虚拟线程的web框架,中篇:完整开发一个支持虚拟线程的quarkus应用
    Windows 安装 汉化版 burp suite
    如何选择一个好的简历模板
    年轻人的第一条SQL查询语句
  • 原文地址:https://blog.csdn.net/ekcchina/article/details/125616033