Introduction

The Application Programmer’s Interface to Python gives C and C++ programmers access to the Python interpreter at a variety of levels.该API同样可以从C ++使用,但为了简洁,它通常被称为Python / C API。使用Python / C API有两个根本不同的原因。第一个原因是为特定目的写入扩展模块;这些是扩展Python解释器的C模块。这可能是最常见的用法。第二个原因是在更大的应用程序中使用Python作为组件;此技术通常在应用程序中称为嵌入 Python。

编写扩展模块是一个比较好理解的过程,其中“食谱”方法工作得很好。有一些工具在某种程度上自动化过程。虽然人们自从早期存在以来就将Python嵌入其他应用程序中,但嵌入Python的过程比编写扩展更不直接。

许多API函数无论是嵌入还是扩展Python都是有用的;此外,嵌入Python的大多数应用程序都需要提供自定义扩展,因此在尝试将Python嵌入到实际应用程序中之前熟悉编写扩展可能是个好主意。

引入头文件

python/c API用到的所有的必须的函数,类型和宏定义都包含在你的代码中,如果引入下面一段代码:

#include "Python.h"

这意味着包括以下标准报头:<stdio.h><string.h><errno.h><limits.h><assert.h><stdlib.h>(如果可用)。

注意

由于Python可能会定义一些影响某些系统上标准头文件的预处理器定义,因此在包含任何标准头文件之前,必须包含Python.h

由Python.h定义的所有用户可见名称(除了由包含的标准头文件定义的名称)具有前缀Py_Py_Py开头的名称由Python实现内部使用,不应由扩展程序使用。结构成员名称没有保留前缀。

重要提示:用户代码不应定义以Py_Py开头的名称。这会使读者感到困惑,并且危及用户代码对未来Python版本的可移植性,后者可能定义以这些前缀之一开头的附加名称。

头文件通常使用Python安装。在Unix中,头文件位于prefix/include/pythonversion/exec_prefix/include/pythonversion/文件夹中, 其中 Python配置脚本的对应参数定义了prefixexec_prefix ,其版本sys.version[:3]在Windows中,头文件位于prefix/include, 其中 prefix 是由安装器指定的安装目录。

要包括头,请将两个目录(如果不同)放在编译器的搜索路径include上。不要将父目录放在搜索路径上,然后使用#include <pythonX.Y/Python.h>;这样会在多平台版本产生问题,因为prefix下平台独立的头文件包含来自exec_prefix中特定的头文件。

C ++用户应该注意,尽管API是完全使用C定义的,头文件正确地声明入口点是extern “C” ,所以没有必要做任何特殊的事情使用C ++的API。

Objects, Types and Reference Counts

大多数Python / C API函数具有一个或多个参数以及类型为PyObject*的返回值。这种类型是一个指向一个表示任意Python对象的不透明数据类型的指针。由于在大多数情况下(例如赋值,范围规则和参数传递),所有Python对象类型都被Python语言以同样的方式处理,因此它们只能由单个C类型表示。几乎所有的Python对象都在堆上:你从来没有声明类型为PyObject的自动或静态变量,只能声明类型为PyObject*的指针变量。唯一的例外是类型对象;因为它们不能被释放,它们通常是静态的PyTypeObject对象。

所有Python对象(甚至Python整数)都有类型引用计数对象的类型确定它是什么类型的对象(例如,整数,列表或用户定义的函数;还有更多,如The standard type hierarchy中所述)。对于每个众所周知的类型,有一个宏来检查对象是否是这种类型;对于实例,PyList_Check(a)为真如果(且只有)由a指向的对象是Python列表。

Reference Counts

引用计数很重要,因为今天的计算机具有有限的(通常严重受限的)内存大小;它计算有多少不同的地方有一个对象的引用。这样的地方可以是另一个对象,或全局(或静态)C变量,或在某些C函数中的局部变量。当对象的引用计数变为零时,将释放该对象。如果它包含对其他对象的引用,则它们的引用计数将递减。那些其他对象可以被依次解除分配,如果这个减少使它们的引用计数变为零,等等。(在这里引用对象的对象存在一个明显的问题;现在,解决方案是“不要这样做。”)

引用计数总是明确处理。正常的方式是使用宏Py_INCREF()将对象的引用计数增加1,并使用Py_DECREF()将其减1。Py_DECREF()宏比increment函数复杂得多,因为它必须检查引用计数是否为零,然后导致对象的释放器被调用。deallocator是一个包含在对象类型结构中的函数指针。类型特定的解除分配器负责递减对象中包含的其他对象的引用计数,如果它是复合对象类型(如列表),以及执行任何需要的附加完成。没有机会引用计数可以溢出; (假设sizeof(Py_ssize_t) = ),则至少使用尽可能多的位来保存引用计数。 sizeof(void *))。因此,引用计数增量是一个简单的操作。

It is not necessary to increment an object’s reference count for every local variable that contains a pointer to an object.理论上,当变量指向它时,对象的引用计数增加1,当变量超出范围时,对象的引用计数减1。但是,这两个相互抵消,所以在结束时引用计数没有改变。使用引用计数的唯一真正原因是为了防止对象被释放,只要我们的变量指向它。如果我们知道至少有一个对象的其他引用至少与我们的变量一样长,则不需要临时增加引用计数。出现这种情况的一个重要情况是将对象作为参数传递给从Python调用的扩展模块中的C函数;调用机制保证在调用期间保持对每个参数的引用。

然而,一个常见的陷阱是从一个列表中提取一个对象并保持一段时间,而不增加其引用计数。一些其他操作可能会从列表中删除对象,减少其引用计数,并可能释放它。真正的危险是无辜的操作可以调用任意的Python代码,这可以做到这一点;有一个代码路径允许控制从Py_DECREF()返回给用户,因此几乎任何操作都是潜在的危险。

一个安全的方法是总是使用通用操作(名称以PyObject_PyNumber_PySequence_PyMapping_这些操作总是增加它们返回的对象的引用计数。这使得调用者有责任在结果完成时调用Py_DECREF();这很快成为第二个性质。

Reference Count Details

Python / C API中函数的引用计数行为最好用引用所有权解释。所有权属于引用,从不属于对象(对象不是所有的:它们总是共享的)。“拥有引用”意味着当不再需要引用时,负责调用Py_DECREF。所有权也可以被转移,这意味着接收引用的所有权的代码然后变得负责最终通过调用Py_DECREF()Py_XDECREF()不再需要 - 或传递这个责任(通常给它的调用者)。当函数将引用的所有权传递给其调用者时,调用者被称为接收引用。当没有所有权转移时,调用者被称为借用引用。对于借用的引用,不需要做任何事情。

相反,当调用函数传递对象的引用时,有两种可能性:函数steals对对象的引用,或者不是。窃取引用意味着当您传递对函数的引用时,该函数假定它现在拥有该引用,并且您不再对它负责。

几个函数偷引用;两个值得注意的异常是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())始终返回一个新的引用(调用者成为引用的所有者)。

重要的是要知道,你是否拥有由函数返回的引用取决于你调用哪个函数 - plumage(作为函数参数传递的对象的类型)doesn不进入!因此,如果使用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;
}

Types

有很少其他数据类型在Python / C API中发挥重要作用;大多数是简单的C类型,例如intlongdoublechar*一些结构类型用于描述用于列出模块导出的函数或新对象类型的数据属性的静态表,另一个用于描述复数的值。这些将与使用它们的功能一起讨论。

Exceptions

Python程序员只需要处理异常,如果需要特定的错误处理;未处理的异常自动传播到调用者,然后传递给调用者的调用者,依此类推,直到它们到达顶级解释器,在那里它们被报告给用户并伴有堆栈追踪。

然而,对于C程序员,错误检查总是必须是显式的。Python / C API中的所有函数都可以引发异常,除非在函数的文档中明确声明。通常,当函数遇到错误时,它会设置一个异常,丢弃它拥有的任何对象引用,并返回一个错误指示符。如果没有记录,则该指示符为NULL-1,具体取决于函数的返回类型。一些函数返回布尔值true / false结果,false表示错误。很少的函数不返回显式错误指示符或具有不明确的返回值,并且需要使用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语句!It illustrates the use of PyErr_ExceptionMatches() and PyErr_Clear() to handle specific exceptions, and the use of Py_XDECREF() to dispose of owned references that may be NULL (note the 'X' in the name; Py_DECREF() would crash when confronted with a NULL reference). 重要的是,用于保存自有引用的变量被初始化为NULL,这样才能工作;同样,建议的返回值被初始化为-1(失败),并且只有在最后调用成功后才设置为成功。

Embedding Python

只有Python解释器的嵌入器(而不是扩展写器)的一个重要任务是担心Python解释器的初始化,也可能是最终化。解释器的大多数功能只能在解释器初始化后使用。

基本初始化函数为Py_Initialize()这将初始化已加载模块的表,并创建builtins__main__sys的基本模块。它还初始化模块搜索路径(sys.path)。

Py_Initialize()不设置“脚本参数列表”(sys.argv)。如果这个变量是稍后执行的Python代码所需要的,则必须使用PySys_SetArgvEx(argc, argv, updatepath ) t>之后,调用Py_Initialize()

在大多数系统上(特别是在Unix和Windows上,尽管细节略有不同),Py_Initialize()根据其对标准Python解析器可执行文件的位置的最佳猜测来计算模块搜索路径,假设Python库位于相对于Python解释器可执行文件的固定位置。In particular, it looks for a directory named lib/pythonX.Y relative to the parent directory where the executable named python is found on the shell command search path (the environment variable PATH).

对于实例,如果Python可执行文件位于/usr/local/bin/python中,它将假设库位于/ usr / local / lib / python X.Y(实际上,当沿着 PATH找不到名为python的可执行文件时,使用此特定路径“用户可以通过设置环境变量 PYTHONHOME覆盖此行为,或通过设置 PYTHONPATH

嵌入应用程序可以在调用Py_Initialize()之前调用Py_SetProgramName(file) 来引导搜索。请注意, PYTHONHOME仍会覆盖此设置,并且 PYTHONPATH仍插入标准路径的前面。需要完全控制的应用程序必须提供其自己的Py_GetPath()Py_GetPrefix()Py_GetExecPrefix()Py_GetProgramFullPath()(所有在Modules/getpath.c中定义)。

有时候,希望“uninitialize”Python。对于实例,应用程序可能需要重新开始(再调用Py_Initialize()),或者应用程序只是使用Python来完成,并且想要释放由Python分配的内存。这可以通过调用Py_Finalize()来完成。如果Python当前处于初始化状态,则函数Py_IsInitialized()返回true。关于这些函数的更多信息在后面的章节中给出。Notice that Py_Finalize() does not free all memory allocated by the Python interpreter, e.g. 扩展模块分配的内存当前无法释放。

Debugging Builds

Python可以用几个宏来构建,以便对解释器和扩展模块进行额外的检查。这些检查往往会对运行时添加大量开销,因此默认情况下不启用它们。

各种类型的调试版本的完整列表位于Python源代码分发中的Misc/SpecialBuilds.txt文件中。构建可用于支持跟踪引用计数,调试内存分配器或主解释器循环的低级概要分析。只有最常用的构建将在本节的剩余部分中描述。

用定义的Py_DEBUG宏编译解释器产生了Python的“调试构建”通常意味着的。通过将--with-pydebug添加到./configure命令,在Unix版本中启用Py_DEBUG它也包含非特定于Python的_DEBUG宏。当在Unix构建中启用Py_DEBUG时,禁用编译器优化。

除了下面描述的引用计数调试之外,还执行以下额外的检查:

  • 额外的检查被添加到对象分配器。
  • 额外的检查被添加到解析器和编译器。
  • 检查从宽类型到窄类型的广播的丢失信息。
  • 许多断言被添加到字典和集实现。此外,设置对象获取test_c_api()方法。
  • 输入参数的正确性检查将添加到框架创建。
  • int的存储使用已知的无效模式初始化,以捕获对未初始化数字的引用。
  • 低级跟踪和异常检查被添加到运行时虚拟机。
  • 额外的检查被添加到内存竞技场实现。
  • 额外的调试被添加到线程模块。

这里可能有额外的检查。

定义Py_TRACE_REFS可启用参考跟踪。当定义时,通过向每个PyObject添加两个额外字段来维护活动对象的循环双向链表。也跟踪总分配。退出时,将打印所有现有引用。(在交互模式下,这发生在解释器运行的每个语句之后)。Py_DEBUG引起。

有关详细信息,请参阅Python源代码分发中的Misc/SpecialBuilds.txt