介绍

应用程序程序员与Python的接口使C和C++程序员可以在不同的层次上访问Python解释器。API同样可以从C++中使用,但为了简洁起见,它通常被称为Python /C API。使用python/c API有两个根本不同的原因。第一个原因是写 扩展模块 为了特定的目的,这些是扩展Python解释器的C模块。这可能是最常见的用法。第二个原因是在更大的应用程序中使用python作为组件;这种技术通常被称为 embedding 应用程序中的python。

编写一个扩展模块是一个相对容易理解的过程,其中“秘诀”方法工作得很好。在某种程度上,有几个工具可以使流程自动化。虽然人们从早期就将Python嵌入到其他应用程序中,但是嵌入Python的过程比编写扩展要简单得多。

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

编码标准

如果您正在编写包含在cpython中的C代码,那么 must 遵循中定义的指南和标准 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可能定义一些影响某些系统上标准头的预处理器定义,所以 must 包括 Python.h 在包含任何标准标题之前。

PY_SSIZE_T_CLEAN 包括之前 Python.h . 见 分析参数并生成值

所有由python.h定义的用户可见名称(由包含的标准头定义的名称除外)都有一个前缀 Py_Py . 名称以开头 _Py 供Python实现内部使用,扩展编写器不应使用。结构成员名称没有保留前缀。

注解

用户代码不应该定义以 Py_Py . 这会混淆读者,并危及用户代码到未来Python版本的可移植性,后者可能会定义以这些前缀之一开头的其他名称。

头文件通常与python一起安装。在Unix上,它们位于目录中 {prefix}/include/pythonversion/{exec_prefix}/include/pythonversion/ 在哪里 prefixexec_prefix 由python的相应参数定义 configure 脚本和 版本'%d.%d' % sys.version_info[:2] . 在Windows上,邮件头安装在 {prefix}/include 在哪里 prefix 是为安装程序指定的安装目录。

要包含头文件,请将两个目录(如果不同)放在编译器的include搜索路径上。做 not 将父目录放在搜索路径上,然后使用 #include <pythonX.Y/Python.h> ;这将在多平台构建上中断,因为 prefix 包括特定于平台的邮件头 exec_prefix .

C++用户应该注意,虽然API完全用C定义,但是头文件正确地声明了入口点。 extern "C" . 因此,不需要做任何特殊的事情来使用来自C++的API。

有用宏指令

在python头文件中定义了几个有用的宏。许多定义更接近于它们的用途(例如 Py_RETURN_NONE )这里定义了其他更通用的实用程序。这不一定是一个完整的列表。

Py_UNREACHABLE()

如果设计无法访问代码路径,请使用此选项。例如,在 default: A中的子句 switch 包含所有可能值的语句 case 声明。在你可能想放一个 assert(0)abort() 打电话。

在释放模式下,宏帮助编译器优化代码,并避免出现无法访问代码的警告。例如,宏是用 __builtin_unreachable() 在释放模式下打开GCC。

用于 Py_UNREACHABLE() 正在跟踪调用一个从未返回但未声明的函数 _Py_NO_RETURN .

如果代码路径不太可能是代码,但在特殊情况下可以到达,则不能使用此宏。例如,在内存不足的情况下,或者如果系统调用返回超出预期范围的值。在这种情况下,最好向调用者报告错误。如果无法将错误报告给调用方, Py_FatalError() 可以使用。

3.7 新版功能.

Py_ABS(x)

返回的绝对值 x .

3.3 新版功能.

Py_MIN(x, y)

返回介于 xy .

3.3 新版功能.

Py_MAX(x, y)

返回最大值 xy .

3.3 新版功能.

Py_STRINGIFY(x)

转换 x 到C字符串。例如。 Py_STRINGIFY(123) 返回 "123" .

3.4 新版功能.

Py_MEMBER_SIZE(type, member)

返回结构的大小 (typemember 以字节为单位。

3.6 新版功能.

Py_CHARMASK(c)

参数必须是范围内的字符或整数 [-128, 127] 或 [0, 255] . 此宏返回 c 铸造成 unsigned char .

Py_GETENV(s)

喜欢 getenv(s) 但回报 NULL 如果 -E 在命令行上传递(即 Py_IgnoreEnvironmentFlag 设置)。

Py_UNUSED(arg)

对于函数定义中未使用的参数,可以使用此选项来消除编译器警告。例子: int func(int a, int Py_UNUSED(b)) {{ return a; }} .

3.4 新版功能.

Py_DEPRECATED(version)

对于已弃用的声明使用此选项。宏必须放在符号名称之前。

例子::

Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);

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

PyDoc_STRVAR(name, str)

创建名为的变量 name 可以在docstring中使用的。如果构建Python时没有docstring,则该值将为空。

使用 PyDoc_STRVAR 对于docstrings支持构建不带docstrings的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},
    // ...
}
PyDoc_STR(str)

为给定的输入字符串创建docstring,如果docstring被禁用,则创建空字符串。

使用 PyDoc_STR 在指定docstrings以支持生成不带docstrings的Python时,如 PEP 7 .

例子::

static PyMethodDef pysqlite_row_methods[] = {
    {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS,
        PyDoc_STR("Returns the keys of the row.")},
    {NULL, NULL}
};

对象、类型和引用计数

大多数python/c api函数都有一个或多个参数以及类型为的返回值 PyObject* . 此类型是指向表示任意Python对象的不透明数据类型的指针。由于在大多数情况下(例如,赋值、作用域规则和参数传递),所有的python对象类型都被python语言以相同的方式处理,所以只适合用一个C类型来表示它们。几乎所有的python对象都存在于堆中:您从不声明类型为的自动或静态变量 PyObject ,只有类型的指针变量 PyObject* 可以声明。唯一的例外是类型对象;因为这些对象永远不能被释放,所以它们通常是静态的 PyTypeObject 物体。

所有的python对象(甚至是python整数)都有一个 type 和A reference count . 一个对象的类型决定了它是什么类型的对象(例如,一个整数、一个列表或一个用户定义的函数;还有更多的,如中所述 标准类型层次结构 )对于每个已知类型,都有一个宏来检查对象是否属于该类型;例如, PyList_Check(a) 如果(并且仅当)指向的对象 a 是一个python列表。

参考计数

引用计数很重要,因为今天的计算机内存大小有限(而且通常非常有限);它计算有多少不同的地方引用了一个对象。这样的位置可以是另一个对象,或者是全局(或静态)C变量,或者是某个C函数中的局部变量。当对象的引用计数为零时,对象将被释放。如果它包含对其他对象的引用,则会减少它们的引用计数。如果这个减量使这些其他对象的引用计数变为零,则可以依次释放这些对象,依此类推。(这里有一个明显的相互引用对象的问题;现在,解决方法是“不要这样做。”)

引用计数总是显式操作。通常的方法是使用宏 Py_INCREF() 将对象的引用计数增加1,并且 Py_DECREF() 把它减量一倍。这个 Py_DECREF() 宏比incref宏复杂得多,因为它必须检查引用计数是否为零,然后调用对象的dealLocator。DealLocator是包含在对象类型结构中的函数指针。类型特定的dealLocator负责减少对象中包含的其他对象的引用计数(如果这是复合对象类型,如列表),并执行所需的任何其他终结。引用计数不可能溢出;至少使用了与虚拟内存中存在不同内存位置一样多的位来保存引用计数(假设 sizeof(Py_ssize_t) >= sizeof(void*) )因此,引用计数增量是一个简单的操作。

对于包含指向对象的指针的每个局部变量,不必增加对象的引用计数。理论上,当变量指向对象时,对象的引用计数会增加一个,当变量超出范围时,对象的引用计数会减少一个。但是,这两个参数相互抵消,所以最后参考计数没有改变。使用引用计数的唯一真正原因是,只要变量指向对象,就可以防止对象被释放。如果我们知道至少有一个对对象的其他引用与我们的变量一样长,那么就不需要临时增加引用计数。出现这种情况的一个重要情况是,对象作为从Python调用的扩展模块中的C函数的参数传递;调用机制保证在调用期间保持对每个参数的引用。

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

安全的方法是始终使用通用操作(名称以 PyObject_PyNumber_PySequence_PyMapping_ )这些操作总是增加它们返回的对象的引用计数。这使主叫方有责任调用 Py_DECREF() 当他们完成了结果,这很快就变成了第二天性。

引用计数详细信息

Python/C API中函数的引用计数行为可以从以下方面得到最好的解释 参考文献的所有权 。所有权与引用有关,而不是与对象相关(对象没有所有权:它们始终是共享的)。“拥有引用”意味着当不再需要引用时负责对其调用Py_DECREF。所有权也可以转移,这意味着接收引用所有权的代码将负责通过调用 Py_DECREF()Py_XDECREF() 当它不再需要的时候-或者把这个责任(通常是给它的调用者)转嫁出去。当函数将引用的所有权传递给其调用方时,调用方被称为接收 new 参考。当没有所有权转移时,调用方被称为 参考资料。不需要做任何事情 borrowed reference

相反,当一个调用函数传入一个对象的引用时,有两种可能:函数 偷窃 对对象的引用,或者不引用。 窃取样板客户 意味着当您传递一个函数的引用时,该函数假定它现在拥有该引用,而您不再对此负责。

很少有函数窃取引用;两个显著的例外是 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()only 设置元组项的方法; PySequence_SetItem()PyObject_SetItem() 拒绝这样做,因为元组是不可变的数据类型。你应该只使用 PyTuple_SetItem() 对于你自己创建的元组。

用于填充列表的等效代码可以使用 PyList_New()PyList_SetItem() .

然而,在实践中,很少使用这些方法来创建和填充元组或列表。有一个通用函数, Py_BuildValue() ,它可以从C值创建最常见的对象,由 format string . 例如,上述两个代码块可以替换为以下代码块(它还负责错误检查)::

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类型,例如 intlongdoublechar* . 一些结构类型用于描述静态表,用于列出模块导出的函数或新对象类型的数据属性,另一个用于描述复数的值。这些将与使用它们的函数一起讨论。

例外情况

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

然而,对于C程序员来说,错误检查总是必须是明确的。除非函数文档中另有明确声明,否则python/c api中的所有函数都会引发异常。通常,当一个函数遇到错误时,它会设置一个异常,丢弃它拥有的任何对象引用,并返回一个错误指示器。如果没有其他记录,则该指标为 NULL-1 ,取决于函数的返回类型。一些函数返回布尔值的真/假结果,其中假表示错误。很少有函数返回没有显式错误指示器或返回值不明确,并且需要显式测试 PyErr_Occurred() . 这些异常总是明确记录的。

在每个线程存储中维护异常状态(这相当于在非线程应用程序中使用全局存储)。线程可以处于两种状态之一:是否发生异常。函数 PyErr_Occurred() 可用于检查:当发生异常时,它返回对异常类型对象的借用引用,以及 NULL 否则。有许多函数可以设置异常状态: PyErr_SetString() 是设置异常状态的最常见(但不是最常见)函数,以及 PyErr_Clear() 清除异常状态。

完全异常状态由三个对象组成(所有这些对象 NULL ):异常类型、对应的异常值和回溯。它们的含义与 sys.exc_info() 但是,它们并不相同:python对象表示由python处理的最后一个异常。 tryexcept 语句,而C级异常状态仅在C函数之间传递异常时存在,直到它到达python字节码解释器的主循环,该循环负责将异常传输到 sys.exc_info() 和朋友们。

注意,从python 1.5开始,从python代码访问异常状态的首选线程安全方法是调用函数 sys.exc_info() 返回python代码的每线程异常状态。此外,访问异常状态的两种方法的语义都发生了变化,因此捕获异常的函数将保存和恢复其线程的异常状态,以保留其调用方的异常状态。这可以防止异常处理代码中的常见错误,这些错误是由一个看似无辜的函数覆盖正在处理的异常引起的;它还减少了跟踪中堆栈帧引用的对象通常不需要的生存期延长。

作为一般原则,调用另一个函数来执行某些任务的函数应该检查被调用的函数是否引发了异常,如果是,则将异常状态传递给它的调用方。它应该丢弃它拥有的任何对象引用,并返回一个错误指示器,但是它应该 not 设置另一个异常——这将覆盖刚刚引发的异常,并丢失有关错误确切原因的重要信息。

检测异常并传递异常的简单示例如 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 */
}

此示例表示 goto 用C语言陈述!它说明了 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代码需要此变量,则必须通过调用 PySys_SetArgvEx(argc, argv, updatepath) 在接到调用后 Py_Initialize() .

在大多数系统上(特别是在UNIX和Windows上,尽管细节略有不同), Py_Initialize() 根据对标准python解释器可执行文件位置的最佳猜测,计算模块搜索路径,假定python库位于相对于python解释器可执行文件的固定位置。特别是,它查找名为 lib/python{X.Y} 相对于可执行文件名为 python 在shell命令搜索路径(环境变量 PATH

例如,如果在 /usr/local/bin/python ,它将假定库位于 /usr/local/lib/python{X.Y} . (实际上,此特定路径也是“回退”位置,在没有名为 python 发现沿着 PATH .)用户可以通过设置环境变量来重写此行为。 PYTHONHOME 或通过设置在标准路径前面插入其他目录 PYTHONPATH .

嵌入应用程序可以通过调用 Py_SetProgramName(file) before 调用 Py_Initialize() .注意 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()not 释放python解释器分配的所有内存,例如,当前无法释放扩展模块分配的内存。

调试生成

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

文件中包含各种类型的调试生成的完整列表。 Misc/SpecialBuilds.txt 在python源代码发行版中。可以使用支持跟踪引用计数、调试内存分配器或主解释器循环的低级分析的生成。本节的其余部分将只描述最常用的构建。

用编译解释器 Py_DEBUG 宏的定义产生了通常所说的Python的“调试构建”。 Py_DEBUG 通过添加 --with-pydebug./configure 命令。它还通过存在非特定于python的 _DEBUG 宏。什么时候? Py_DEBUG 在Unix内部版本中启用,编译器优化被禁用。

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

  • 额外的检查被添加到对象分配器中。

  • 额外的检查被添加到解析器和编译器中。

  • 检查从宽类型到窄类型的向下转换是否丢失信息。

  • 许多断言被添加到字典和集合实现中。此外,set对象获取 test_c_api() 方法。

  • 输入参数的健全性检查被添加到框架创建中。

  • 用已知的无效模式初始化ints存储,以捕获对未初始化数字的引用。

  • 低级跟踪和额外的异常检查被添加到运行时虚拟机中。

  • 额外的检查被添加到内存竞技场实现中。

  • 额外的调试将添加到线程模块中。

这里可能没有提到其他支票。

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

请参考 Misc/SpecialBuilds.txt 在python源代码发行版中获取更详细的信息。