简介

应用程序程序员的Python接口使C和C ++程序员可以在各种级别访问Python解释器API可以与C ++同等地使用,但为了简洁起见,它通常被称为Python / CAPI。使用Python / C API有两个根本不同的原因。第一个原因是为特定目的写扩展模块;这些是扩展Python解释器的C模块。这可能是最常用的。第二个原因是使用Python作为大型应用程序中的一个组件;这种技术通常被称为嵌入 Pythonin一个应用程序.

编写扩展模块是一个相对容易理解的过程,其中“烹饪书”方法很有效。有几种工具可以在一定程度上自动化过程。虽然人们在早期存在的过程中将Python嵌入到其他应用程序中,但嵌入Python的过程并不比编写扩展程序简单得多.

许多API函数都是有用的,无论你是否嵌入了原始Python;此外,大多数嵌入Python的应用程序也需要提供自定义扩展,因此在尝试将Python嵌入到重新应用之前熟悉编写扩展可能是个好主意.

编码标准

如果您正在编写包含在CPython中的C代码,那么你必须遵循PEP 7 。这些指南适用于您所贡献的Python版本。除非你最终希望将它们贡献给Python,否则你的第三方扩展模块不需要遵循这些约定.

 

包含文件

使用Python /所需的所有函数,类型和宏定义C API包含在您的代码中,如下所示:

#include "Python.h"

这意味着包含以下标准标题:<stdio.h>,<string.h>, <errno.h>, <limits.h>, <assert.h><stdlib.h>(如果可用).

注意

由于Python可能会定义一些影响某些系统上标准头的预处理器定义,所以在包含任何标准头之前你must包含Python.h

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

重要的用户代码永远不应该定义以Py_Py开头的名称。这会使读者感到困惑,并危及用户代码对未来Python版本的可移植性,这些版本可能会定义以这些前缀之一开头的其他名称.

头文件通常与Python一起安装。在Unix上,它们位于prefix/include/pythonversion/exec_prefix/include/pythonversion/目录中,其中prefixexec_prefix由Python的相应参数定义配置脚本和version"%d.%d" % sys.version_info[:2]。在Windows上,标题安装在prefix/include中,其中prefix是指定给安装程序的安装目录.

要包含标题,请将两个目录(如果不同)放在编译器上包含的搜索路径。做not将父目录放在搜索路径上,然后使用#include <pythonX.Y/Python.h>;这将打破多平台构建,因为prefix下面的平台独立头包含来自exec_prefix.

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

有用的宏

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

Py_UNREACHABLE ()
当你有一个你不希望达到的代码路径时使用这个。例如,在default:声明switch声明中有关可能的值的声明包含在case声明中。在你可能想要放置assert(0)abort()来电的地方使用它

在版本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
将此用于函数定义中未使用的参数,以使编译警告静音,例如PyObject* func(PyObject *Py_UNUSED(ignored)).

版本3.4.

 

中的新增内容,类型和引用计数

大多数Python / C API函数都有一个或多个参数以及类型的返回值PyObject*。此类型是指向表示任意Python对象的不透明数据类型的指针。由于在大多数情况下(例如,赋值,范围规则和参数传递),Python语言都以相同的方式处理所有Python对象类型,因此它们应该由单个C类型表示。几乎所有Python对象都存在于堆中:你永远不会声明类型为PyObject,只能声明PyObject*类型的指针变量。唯一的例外是类型对象;因为它们绝对不能被分配,所以它们通常是静态的PyTypeObject对象.

所有Python对象(甚至Python整数)都有typereference count。对象的类型确定它是什么类型的对象(例如,整数,列表或用户定义的函数;还有更多的问题在中标准类型层次结构)。对于每个众所周知的类型,都有一个macroto检查对象是否属于该类型;例如, PyList_Check(a)istrue if(且仅当)a指向的对象是一个Python列表.

 

引用计数

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

引用计数总是被明确地操作。通常的方法是使用宏Py_INCREF()将对象的引用计数增加1,并使用Py_DECREF()将其减1。Py_DECREF()macrois比incrementf要复杂得多,因为它必须检查引用计数是否为零然后导致对象的解除分配器被置换。deallocator是包含在对象的类型结构中的函数指针。特定于类型的解除分配器负责减少对象中包含的其他对象的干扰计数(如果这是复合对象类型,例如列表),以及执行所需的任何其他完成。引用计数不可能溢出;由于虚拟内存中存在明显的内存位置(假设sizeof(Py_ssize_t) >= sizeof(void*)),因此至少会使用多少位来保存引用计数。因此,引用计数增量是一个简单的操作.

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

但是,常见的缺陷是从列表中提取对象并保持一段时间而不增加其引用计数。其他一些操作可能会从列表中删除该对象,减少其引用计数并可能解除分配。真正的危险是,无辜的操作可能会调用可以执行此操作的任意Python代码;有一个代码路径,允许控件从回流到用户Py_DECREF()最重要的是任何操作都有潜在危险.

一种安全的方法是始终使用泛型操作(namebegins与PyObject_, PyNumber_, PySequence_要么 PyMapping_这些操作总是增加它们返回的对象的引用计数。这使得调用者有责任调用Py_DECREF()当他们完成结果;这很快成为第二天性.

 

引用计数详细信息

Python / C API中函数的引用计数行为最好用ownership of references。所有权属于引用,neverto对象(对象不归属于:它们始终是共享的)。“拥有参考”意味着当不再需要参考时负责在其上调用Py_DECREF。所有权也可以被转移,这意味着接受参考所有权的代码然后通过在不再需要时调用Py_DECREF()Py_XDECREF()或者传递这一责任而成为负责人,从而降低它的负担(通常是它的召唤者)。当函数将引用的所有权传递给其调用者时,调用者被称为接收new引用。当没有所有权转移时,呼叫者被称为borrow参考。没有什么需要为明天的引用而做.

相反,当一个调用函数传入一个对象的引用时,有两种可能性:函数steals对象的引用,或者它不是。Stealing a reference表示当你传递一个函数的引用时,该函数假定它现在拥有该引用,并且你不再对它负责.

很少有函数窃取引用;这两个值得注意的例外是PyList_SetItem()PyTuple_SetItem(),它们窃取了对项目的引用(但不是对项目所放置的元组或列表!)。这些函数被设计为窃取引用,因为有一个常见的习惯用于使用新创建的对象来填充元组或列表;例如,代码创建元组(1, 2, "three")可能看起来像这样(暂时忘记abouterror处理;更好的编码方式如下所示):

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(),它可以用format string指定从C值创建大多数常见对象。例如,上面两个代码块可以用以下代码替换(也需要关心错误检查):

PyObject *tuple, *list;
tuple = Py_BuildValue("(iis)", 1, 2, "three");
list = Py_BuildValue("[iis]", 1, 2, "three");

更常见的是使用PyObject_SetItem()和朋友的项目,这些项目只是你借用的参考文献,就像你正在编写的函数中传递的参数一样。在这种情况下,他们对参考计数的行为更加明智,因为您不必增加引用计数,因此您可以提供参考(“让它被盗”)。例如,此函数将列表中的所有项(实际上是任何可变序列)设置为给定项:

intset_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(),总是返回一个新的引用(调用者成为引用的所有者).

重要的是意识到你是否拥有一个由函数返回的引用取决于你只调用哪个函数 – the plumage(作为参数传递给函数的对象的类型)doesn’t enter into it!因此,如果你提取一个项目从使用PyList_GetItem()列表中,你不拥有引用 – 但如果你使用PySequence_GetItem()(它恰好采用完全相同的参数),你拥有对返回对象的引用.

下面是一个示例,说明如何编写一个函数来计算整数列表中项目的总和;一次使用PyList_GetItem(),一次使用PySequence_GetItem().

longsum_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;
}
longsum_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 / CAPI中发挥重要作用;大多数是简单的C类型,如int, long,doublechar*。一些结构类型用于描述用于列出模块导出的函数的静态表或新对象类型的数据属性,另一种用于描述复数的值。这些将与使用它们的功能一起讨论.

 

例外

如果需要特定的错误处理,Python程序员只需要处理异常;未处理的异常会自动传播给调用者,然后传递给调用者的调用者,依此类推,直到他们到达顶级解释器,然后将它们报告给用户并伴随stacktraceback.

然而,对于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处理的最后一个异常tryexcept语句,而C级异常状态只存在,而C函数之间传递异常,直到它到达Pythonbytecode解释器的主循环,它负责将它传递给sys.exc_info()和friends.

请注意,从Python 1.5开始,从Python代码访问异常状态的首选,线程安全方法是调用函数sys.exc_info(),它返回Python代码的每线程异常状态。此外,访问异常状态的两种方法的这些语法已经改变,因此捕获异常的功能将保存并恢复其线程的异常状态,以便保留其调用者的异常状态。这可以防止异常处理代码中的commonbugs由一个无辜的函数覆盖正在处理的异常;它还减少了由追踪中的堆栈帧引用的对象经常不需要的生命周期扩展.

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

sum_sequence()上面的例子。碰巧这个例子在检测到错误时不需要清理任何拥有的引用。followingexample函数显示了一些错误清理。首先,为了提醒你为什么喜欢Python,我们展示了相应的Python代码:

def incr_item(dict, key):
    try:
        item = dict[key]
    except KeyError:
        item = 0
    dict[key] = item + 1

这是相应的C代码,它的所有荣耀:

intincr_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 */
}

 

这个例子代表了gotoC中的陈述!它说明了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 interpreterexecutable位置的最佳猜测来计算模块搜索路径,假设Python库位于与Python解释器可执行文件相关的固定位置。特别是,它寻找名为lib/pythonX.Y相对于名为python在shell命令searchpath中找到(环境变量PATH).

例如,如果在/usr/local/bin/python,它会假设库在/usr/local/lib/pythonX.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当前处于初始化状态,则返回returntrue。有关这些功能的更多信息将在后面的章节中给出。注意Py_FinalizeEx()not释放Python解释器分配的所有内存,例如:目前无法释放由扩展模块分配的内存.

 

调试构建

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

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

使用Py_DEBUG定义的宏编译解释器产生的通常意味着Python的“调试版本”。Py_DEBUG通过在--with-pydebug中添加./configure命令。特定于soot-Python的_DEBUG宏的存在也暗示了这一点。当Py_DEBUG启用Unix构建时,编译器优化被禁用.

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

  • 将额外的检查添加到对象分配器.
  • 将额外的检查添加到解析器和编译器中.
  • 检查从宽类型到窄类型的丢弃信息是否丢失信息.
  • 许多断言被添加到字典并设置实现。此外,set对象获取test_c_api()方法.
  • 输入参数的时间检查被添加到帧创建中.
  • int的存储使用已知的无效模式进行初始化,以捕获对未初始化数字的引用.
  • 低级跟踪和额外的异常检查被添加到runtimevirtual machine.
  • 额外的检查被添加到内存竞技场实现中.
  • 额外的调试被添加到线程模块中.

这里可能没有提到额外的检查.

定义Py_TRACE_REFS启用参考跟踪。定义时,通过在每个PyObject。还会跟踪总分配。Uponexit,打印所有现有参考。(在交互模式下,这将在解释器运行的每个语句之后发生。) 暗示Py_DEBUG.

请参阅Python源代码中的Misc/SpecialBuilds.txt以获取更多详细信息.

评论被关闭。