1. 用 C 或 C 扩展 Python

如果您知道如何使用 C 进行编程,则向 Python 添加新的内置模块非常容易。此类扩展模块可以完成两件事,而这是直接在 Python 中无法完成的:它们可以实现新的内置对象类型,它们可以调用 C 库函数和系统调用。

为了支持扩展,Python API(应用程序员接口)定义了一组函数,宏和变量,这些函数,宏和变量提供对 Python 运行时系统大多数方面的访问。pass包含 Headers"Python.h"将 Python API 合并到 C 源文件中。

扩展模块的编译取决于其预期用途以及您的系统设置。后面的章节中提供了详细信息。

Note

C 扩展接口特定于 CPython,并且扩展模块不适用于其他 Python 实现。在许多情况下,可以避免编写 Cextensions 并保留对其他实现的可移植性。例如,如果您的用例是调用 C 库函数或系统调用,则应考虑使用ctypes模块或cffi库,而不是编写自定义 C 代码。pass这些模块,您可以编写 Python 代码与 C 代码进行接口,并且在 Python 的实现之间比编写和编译 C 扩展模块更容易移植。

1.1. 一个简单的例子

让我们创建一个名为spam的扩展模块(Monty Python 爱好者最喜欢的食物……),并且我们想为 C 库函数system() [1]创建一个 Python 接口。该函数将以零结尾的字符串作为参数,并返回一个整数。我们希望可以从 Python 调用此函数,如下所示:

>>> import spam
>>> status = spam.system("ls -l")

首先创建一个文件spammodule.c。 (从历史上看,如果模块名为spam,则包含其实现的 C 文件称为spammodule.c;如果模块名称很长,如spammify,则模块名称只能为spammify.c.)

我们文件的第一行可以是:

#include <Python.h>

插入 Python API(如果需要,您可以添加描述模块目的的 Comments 和版权语句)。

Note

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

Python.h定义的所有用户可见符号的前缀为PyPY,但标准头文件中定义的符号除外。为了方便起见,由于"Python.h"被 Python 解释器广泛使用,因此"Python.h"包含一些标准头文件:<stdio.h><string.h><errno.h><stdlib.h>。如果系统上不存在后一个头文件,它将直接语句函数malloc()free()realloc()

我们添加到模块文件中的第二件事是 C 函数,该函数将在评估 Python 表达式spam.system(string)时被调用(我们将很快看到它finally如何被调用):

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return Py_BuildValue("i", sts);
}

从 Python 中的参数列表(例如,单个表达式"ls -l")到传递给 C 函数的参数有直接的转换。 C 函数始终有两个参数,通常称为* self args *。

对于模块Function,* self 参数为 NULL *或在初始化模块时选择的指针(请参见Py_InitModule4())。对于方法,它将指向对象实例。

  • args *参数将是指向包含参数的 PythonTuples 对象的指针。Tuples 的每个项目对应于调用的参数列表中的一个参数。参数是 Python 对象-为了在 C 函数中对其进行任何处理,我们必须将其转换为 C 值。 Python API 中的函数PyArg_ParseTuple()会检查参数类型并将其转换为 C 值。它使用模板字符串来确定所需的参数类型以及将转换后的值存储在其中的 C 变量的类型。稍后再详细介绍。

如果所有参数都具有正确的类型并且其组成部分已存储在传递了地址的变量中,则PyArg_ParseTuple()返回 true(非零)。如果传递了无效的参数列表,它将返回 false(零)。在后一种情况下,它还会引发适当的异常,因此调用函数可以立即返回* NULL *(如示例中所示)。

1.2. Intermezzo:错误和异常

Python 解释器中的一个重要约定如下:当一个函数失败时,它应该设置一个异常条件并返回一个错误值(通常是* NULL 指针)。异常存储在解释器内部的静态全局变量中;如果此变量为 NULL *,则不会发生异常。第二个全局变量存储异常的“关联值”(raise的第二个参数)。第三个变量包含堆栈 traceback,以防错误源于 Python 代码。这三个变量是 Python 变量sys.exc_typesys.exc_valuesys.exc_traceback的 C 等效项(请参见 Python 库参考中有关模块sys的部分)。重要的是要了解它们,以了解如何传递错误。

Python API 定义了许多函数来设置各种类型的异常。

最常见的是PyErr_SetString()。它的参数是一个异常对象和一个 C 字符串。异常对象通常是 sched 义的对象,例如PyExc_ZeroDivisionError。 C 字符串指示错误的原因,并转换为 Python 字符串对象,并存储为异常的“关联值”。

另一个有用的函数是PyErr_SetFromErrno(),该函数只接受一个异常参数,并pass检查全局变量errno构造关联的值。最通用的函数是PyErr_SetObject(),它带有两个对象参数,即异常及其关联的值。您不需要Py_INCREF()传递给任何这些函数的对象。

您可以非破坏性地测试是否已使用PyErr_Occurred()设置了异常。这将返回当前的异常对象,如果未发生异常,则返回* NULL *。通常您不需要调用PyErr_Occurred()来查看函数调用中是否发生了错误,因为您应该能够从返回值中得知。

当调用另一个函数* g 的函数 f 检测到后者失败时, f 自身应返回错误值(通常为 NULL -1)。它不应该调用PyErr_*()函数之一-* g 已经调用了其中一个。然后, f 的调用者还应该向调用者返回错误指示,再次不*调用PyErr_*(),依此类推-导致错误的最详细原因已由首先检测到该错误的函数报告。错误到达 Python 解释器的主循环后,这将中止当前正在执行的 Python 代码,并try查找 Python 程序员指定的异常处理程序。

(在某些情况下,模块实际上可以pass调用另一个PyErr_*()函数来提供更详细的错误消息,在这种情况下,这样做是可以的.但是,一般而言,这不是必需的,并且可能导致有关错误丢失的原因:大多数操作可能由于各种原因而失败.)

要忽略失败的函数调用设置的异常,必须pass调用PyErr_Clear()明确清除异常条件。 C 代码唯一应调用PyErr_Clear()的时间是:它不希望将错误传递给解释器,而是希望完全自行处理(可能passtry其他操作,或 Feign 没有出问题)。

每个失败的malloc()调用都必须转换为异常— malloc()(或realloc())的直接调用者必须调用PyErr_NoMemory()并返回故障指示符本身。所有对象创建函数(例如PyInt_FromLong())都已执行此操作,因此此 Comments 仅与直接调用malloc()的对象有关。

还应注意,除了PyArg_ParseTuple()和朋友之外,重要的是返回整数状态的函数通常返回正值或零以表示成功,返回-1表示失败,例如 Unix 系统调用。

最后,当返回错误指示符时,请小心清理垃圾(pass对已创建的对象进行Py_XDECREF()Py_DECREF()调用)!

提出哪种 exceptions 完全取决于您。有一些预语句的 C 对象,它们与所有内置 Python 异常(例如PyExc_ZeroDivisionError)相对应,您可以直接使用它们。当然,您应该明智地选择异常-不要使用PyExc_TypeError来表示无法打开文件(应该是PyExc_IOError)。如果参数列表有问题,则PyArg_ParseTuple()函数通常会引发PyExc_TypeError。如果您的参数的值必须在特定范围内或必须满足其他条件,则PyExc_ValueError是适当的。

您还可以定义模块唯一的新异常。为此,通常在文件开头语句一个静态对象变量:

static PyObject *SpamError;

并使用异常对象在模块的初始化函数(initspam())中对其进行初始化(暂时不进行错误检查):

PyMODINIT_FUNC
initspam(void)
{
    PyObject *m;

    m = Py_InitModule("spam", SpamMethods);
    if (m == NULL)
        return;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_INCREF(SpamError);
    PyModule_AddObject(m, "error", SpamError);
}

请注意,异常对象的 Python 名称是spam.errorPyErr_NewException()函数可以创建一个 Base Class 为Exception的类(除非传入了另一个类而不是* NULL *),如Built-in Exceptions中所述。

另请注意,SpamError变量保留对新创建的异常类的引用;这是故意的!由于可以pass外部代码将异常从模块中删除,因此需要对该类的拥有的引用,以确保不会将其丢弃,从而使SpamError成为悬空指针。如果它变成了悬空的指针,则引发异常的 C 代码可能会导致核心转储或其他意外的副作用。

在本示例的后面,我们讨论使用PyMODINIT_FUNC作为函数返回类型。

可以pass调用PyErr_SetString()在扩展模块中引发spam.error异常,如下所示:

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}

1.3. 回到例子

回到我们的示例函数,您现在应该能够理解以下语句:

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;

如果根据PyArg_ParseTuple()设置的异常在参数列表中检测到错误,则返回* NULL *(函数返回对象指针的错误指示符)。否则,参数的字符串值已复制到局部变量command。这是一个指针分配,您不应修改其指向的字符串(因此在标准 C 中,变量command应该正确语句为const char *command)。

下一条语句是对 Unix 函数system()的调用,将我们刚从PyArg_ParseTuple()获得的字符串传递给它:

sts = system(command);

我们的spam.system()函数必须将sts的值作为 Python 对象返回。这是pass函数Py_BuildValue()完成的,它类似于PyArg_ParseTuple()的反函数:它采用格式字符串和任意数量的 C 值,并返回一个新的 Python 对象。稍后会提供有关Py_BuildValue()的更多信息。

return Py_BuildValue("i", sts);

在这种情况下,它将返回一个整数对象。 (是的,甚至整数都是 Python 中堆上的对象!)

如果您有一个 C 函数不返回任何有用的参数(一个函数返回void),则相应的 Python 函数必须返回None。您需要使用以下惯用法(由Py_RETURN_NONE宏实现):

Py_INCREF(Py_None);
return Py_None;

Py_None是特殊 Python 对象None的 C 名称。正如我们所看到的,它是一个 true 的 Python 对象,而不是* NULL *指针,这在大多数情况下都意味着“错误”。

1.4. 模块的方法表和初始化Function

我答应展示如何从 Python 程序中调用spam_system()。首先,我们需要在“方法表”中列出其名称和地址:

static PyMethodDef SpamMethods[] = {
    ...
    {"system",  spam_system, METH_VARARGS,
     "Execute a shell command."},
    ...
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

请注意第三个条目(METH_VARARGS)。这是一个标志,告诉解释器要用于 C 函数的调用约定。通常应始终为METH_VARARGSMETH_VARARGS | METH_KEYWORDS0的值表示已使用PyArg_ParseTuple()的过时变体。

当仅使用METH_VARARGS时,该函数应该期望将 Python 级别的参数作为 Tuples 传递,以便passPyArg_ParseTuple()进行解析;下面提供了有关此Function的更多信息。

如果关键字参数应传递给函数,则可以在第三字段中设置METH_KEYWORDS位。在这种情况下,C 函数应接受第三个PyObject *参数,它将是关键字的字典。使用PyArg_ParseTupleAndKeywords()解析此类函数的参数。

方法表必须传递给模块初始化函数中的解释器。初始化函数必须命名为initname(),其中* name *是模块的名称,并且应该是模块文件中定义的唯一非static项:

PyMODINIT_FUNC
initspam(void)
{
    (void) Py_InitModule("spam", SpamMethods);
}

请注意,PyMODINIT_FUNC 将函数语句为void返回类型,语句平台所需的任何特殊链接语句,对于 C 而言,将函数语句为extern "C"

Python 程序首次导入模块spam时,将调用initspam()。 (有关嵌入 Python 的 Comments,请参见下文.)它调用Py_InitModule(),创建一个“模块对象”(插入到字典sys.modules的键"spam"下),并将内置函数对象插入基于该对象的新创建的模块中。作为第二个参数传递的表(由PyMethodDef个结构组成的数组)。 Py_InitModule()返回指向它创建的模块对象的指针(此处未使用)。对于某些错误,它可能会由于致命错误而中止;如果无法令人满意地初始化模块,则返回* NULL *。

嵌入 Python 时,除非_PyImport_Inittab表中有一个条目,否则不会自动调用initspam()函数。解决此问题的最简单方法是在调用Py_Initialize()之后直接调用initspam()来静态初始化静态链接的模块:

int
main(int argc, char *argv[])
{
    /* Pass argv[0] to the Python interpreter */
    Py_SetProgramName(argv[0]);

    /* Initialize the Python interpreter.  Required. */
    Py_Initialize();

    /* Add a static module */
    initspam();

    ...

可以在 Python 源代码发布的文件Demo/embed/demo.c中找到一个示例。

Note

sys.modules中删除条目或将已编译的模块导入进程中的多个解释器中(或在fork()之后但没有插入exec()的情况下),可能会对某些扩展模块造成问题。扩展模块作者在初始化内部数据结构时应谨慎行事。还要注意,reload()函数可以与扩展模块一起使用,并且将调用模块初始化函数(在示例中为initspam()),但是如果是从可动态加载的目标文件(Unix 上为.so,则为.dll)加载的,则不会再次加载该模块。在 Windows 上)。

Python 源代码发行版中包含一个更重要的示例模块Modules/xxmodule.c。该文件可以用作模板或仅作为示例阅读。

1.5. 编译与链接

使用新扩展之前,还有两件事要做:编译并将其与 Python 系统链接。如果使用动态加载,则详细信息可能取决于系统使用的动态加载的样式。有关更多信息,请参阅有关构建扩展模块的章节(第使用 distutils 构建 C 和 C 扩展章)和仅与在 Windows 上构建有关的其他信息(第在 Windows 上构建 C 和 C 扩展章)。

如果您不能使用动态加载,或者要使模块成为 Python 解释器的永久组成部分,则必须更改配置设置并重新构建解释器。幸运的是,这在 Unix 上非常简单:只需将您的文件(例如spammodule.c)放置在未打包的源代码发布的Modules/目录中,然后在文件Modules/Setup.local上添加一行描述文件:

spam spammodule.o

并pass在顶级目录中运行 make 来重建解释器。您也可以在Modules/子目录中运行 make ,但是必须首先pass运行' make Makefile'在其中重建Makefile。 (每次更改Setup文件时,这都是必需的.)

如果您的模块需要链接其他库,则这些库也可以列在配置文件中的行上,例如:

spam spammodule.o -lX11

1.6. 从 C 调用 Python 函数

到目前为止,我们专注于使 C 函数可从 Python 调用。反之亦有用:从 C 调用 Python 函数。对于支持所谓的“回调”函数的库,尤其如此。如果 C 接口使用了回调,则等效的 Python 通常需要为 Python 程序员提供回调机制。实现将需要从 C 回调中调用 Python 回调函数。其他用途也是可以想象的。

幸运的是,Python 解释器很容易递归调用,并且有一个标准接口来调用 Python 函数。 (我不会讨论如何使用特定的字符串作为 Importing 来调用 Python 解析器-如果您有兴趣,请查看 python 源代码中Modules/main.c-c命令行选项的实现。)

调用 Python 函数很容易。首先,Python 程序必须以某种方式将 Python 函数对象传递给您。您应该提供一个Function(或其他接口)来执行此操作。调用此函数时,请将指向 Python 函数对象的指针(小心Py_INCREF()!)保存在全局变量(或您认为合适的位置)中。例如,以下Function可能是模块定义的一部分:

static PyObject *my_callback = NULL;

static PyObject *
my_set_callback(PyObject *dummy, PyObject *args)
{
    PyObject *result = NULL;
    PyObject *temp;

    if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
        if (!PyCallable_Check(temp)) {
            PyErr_SetString(PyExc_TypeError, "parameter must be callable");
            return NULL;
        }
        Py_XINCREF(temp);         /* Add a reference to new callback */
        Py_XDECREF(my_callback);  /* Dispose of previous callback */
        my_callback = temp;       /* Remember new callback */
        /* Boilerplate to return "None" */
        Py_INCREF(Py_None);
        result = Py_None;
    }
    return result;
}

此Function必须使用METH_VARARGS标志在解释器中注册; 模块的方法表和初始化Function部分对此进行了说明。 在扩展函数中提取参数部分中记录了PyArg_ParseTuple()函数及其参数。

Py_XINCREF()Py_XDECREF()递增/递减对象的引用计数,并且在存在* NULL 指针的情况下是安全的(但请注意,在这种情况下, temp 不会为 NULL *)。有关它们的更多信息,请参见Reference Counts部分。

稍后,当需要调用该函数时,可以调用 C 函数PyObject_CallObject()。这个函数有两个参数,两个都是指向任意 Python 对象的指针:Python 函数和参数列表。参数列表必须始终是一个 Tuples 对象,其长度是参数的数量。要不带任何参数调用 Python 函数,请传入 NULL 或一个空的 Tuples。要用一个参数调用它,请传递一个单例 Tuples。当Py_BuildValue()的格式字符串由括号之间的零个或多个格式代码组成时,它返回一个 Tuples。例如:

int arg;
PyObject *arglist;
PyObject *result;
...
arg = 123;
...
/* Time to call the callback */
arglist = Py_BuildValue("(i)", arg);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);

PyObject_CallObject()返回 Python 对象指针:这是 Python 函数的返回值。 PyObject_CallObject()就其参数而言是“引用计数中立”的。在该示例中,创建了一个新的 Tuples 作为参数列表,该数组在PyObject_CallObject()调用之后立即被Py_DECREF() -ed。

PyObject_CallObject()的返回值是“ new”:它是一个全新的对象,或者是其引用计数已增加的现有对象。因此,除非您不希望将其保存在全局变量中,否则您应该以某种方式Py_DECREF()结果,即使您(尤其是对它的值)不感兴趣也是如此。

但是,在执行此操作之前,请务必检查返回值不是* NULL *。如果是,则 Python 函数pass引发异常来终止。如果从 Python 调用了名为PyObject_CallObject()的 C 代码,则它现在应该向其 Python 调用者返回错误指示,以便解释器可以打印堆栈跟踪,或者调用的 Python 代码可以处理异常。如果不可能或不希望这样做,则应pass调用PyErr_Clear()清除异常。例如:

if (result == NULL)
    return NULL; /* Pass error back */
...use result...
Py_DECREF(result);

根据所需的 Python 回调函数接口,您可能还必须向PyObject_CallObject()提供参数列表。在某些情况下,Python 程序还会pass指定回调函数的相同接口提供参数列表。然后可以将其保存并以与Function对象相同的方式使用。在其他情况下,您可能必须构造一个新的 Tuples 作为参数列表传递。最简单的方法是调用Py_BuildValue()。例如,如果要传递整数事件代码,则可以使用以下代码:

PyObject *arglist;
...
arglist = Py_BuildValue("(l)", eventcode);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);

请注意,在调用后紧接错误检查之前Py_DECREF(arglist)的位置!另请注意,严格来说,此代码不完整:Py_BuildValue()可能会用完内存,因此应进行检查。

您也可以使用PyObject_Call()调用带有关键字参数的函数,该函数支持参数和关键字参数。如上例所示,我们使用Py_BuildValue()构造字典。

PyObject *dict;
...
dict = Py_BuildValue("{s:i}", "name", val);
result = PyObject_Call(my_callback, NULL, dict);
Py_DECREF(dict);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);

1.7. 在扩展函数中提取参数

PyArg_ParseTuple()函数语句如下:

int PyArg_ParseTuple(PyObject *arg, char *format, ...);
  • arg *参数必须是一个 Tuples 对象,其中包含从 Python 传递到 C 函数的参数列表。 * format *参数必须是格式字符串,其语法在《 Python/C API 参考手册》的解析参数和构建值中进行了说明。其余参数必须是类型由格式字符串确定的变量的地址。

请注意,虽然PyArg_ParseTuple()检查 Python 参数是否具有必需的类型,但它无法检查传递给调用的 C 变量的地址的有效性:如果在那里出错,则代码可能会崩溃或至少覆盖内存中的随机位。所以要小心!

请注意,提供给调用方的任何 Python 对象引用都是“借用”引用;不要减少其参考计数!

一些示例调用:

int ok;
int i, j;
long k, l;
const char *s;
int size;

ok = PyArg_ParseTuple(args, ""); /* No arguments */
    /* Python call: f() */
ok = PyArg_ParseTuple(args, "s", &s); /* A string */
    /* Possible Python call: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
    /* Possible Python call: f(1, 2, 'three') */
ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
    /* A pair of ints and a string, whose size is also returned */
    /* Possible Python call: f((1, 2), 'three') */
{
    const char *file;
    const char *mode = "r";
    int bufsize = 0;
    ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
    /* A string, and optionally another string and an integer */
    /* Possible Python calls:
       f('spam')
       f('spam', 'w')
       f('spam', 'wb', 100000) */
}
{
    int left, top, right, bottom, h, v;
    ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
             &left, &top, &right, &bottom, &h, &v);
    /* A rectangle and a point */
    /* Possible Python call:
       f(((0, 0), (400, 300)), (10, 10)) */
}
{
    Py_complex c;
    ok = PyArg_ParseTuple(args, "D:myfunction", &c);
    /* a complex, also providing a function name for errors */
    /* Possible Python call: myfunction(1+2j) */
}

1.8. 扩展Function的关键字参数

PyArg_ParseTupleAndKeywords()函数语句如下:

int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
                                char *format, char *kwlist[], ...);
  • arg format *参数与PyArg_ParseTuple()函数的参数相同。 * kwdict *参数是从 Python 运行时作为第三个参数接收的关键字的词典。 * kwlist 参数是一个 NULL 终止的字符串列表,用于标识参数;名称与 format *中的类型信息从左到右匹配。成功时,PyArg_ParseTupleAndKeywords()返回 true,否则返回 false 并引发适当的异常。

Note

使用关键字参数时,无法解析嵌套 Tuples!传入的关键字参数在* kwlist *中不存在,将引发TypeError

这是一个基于 Geoff Philbrick([email protected])的示例的使用关键字的示例模块:

#include "Python.h"

static PyObject *
keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
{
    int voltage;
    char *state = "a stiff";
    char *action = "voom";
    char *type = "Norwegian Blue";

    static char *kwlist[] = {"voltage", "state", "action", "type", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist,
                                     &voltage, &state, &action, &type))
        return NULL;

    printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
           action, voltage);
    printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);

    Py_INCREF(Py_None);

    return Py_None;
}

static PyMethodDef keywdarg_methods[] = {
    /* The cast of the function is necessary since PyCFunction values
     * only take two PyObject* parameters, and keywdarg_parrot() takes
     * three.
     */
    {"parrot", (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
     "Print a lovely skit to standard output."},
    {NULL, NULL, 0, NULL}   /* sentinel */
};
void
initkeywdarg(void)
{
  /* Create the module and add the functions */
  Py_InitModule("keywdarg", keywdarg_methods);
}

1.9. 构建任意价值

此Function是PyArg_ParseTuple()的对应Function。语句如下:

PyObject *Py_BuildValue(char *format, ...);

它可以识别一组类似于PyArg_ParseTuple()识别的格式单元,但是参数(Importing 到函数而不是输出)必须不是指针,而只能是值。它返回一个新的 Python 对象,适合从从 Python 调用的 C 函数返回。

PyArg_ParseTuple()的区别:后者要求其第一个参数为 Tuples(因为 Python 参数列表始终在内部表示为 Tuples),而Py_BuildValue()并不总是构建 Tuples。仅当其格式字符串包含两个或多个格式单位时,才会构建 Tuples。如果格式字符串为空,则返回None;如果它仅包含一个格式单元,则返回该格式单元描述的任何对象。要强制其返回大小为 0 或 1 的 Tuples,请用括号括起格式字符串。

示例(在调用的左侧,在右侧为生成的 Python 值):

Py_BuildValue("")                        None
Py_BuildValue("i", 123)                  123
Py_BuildValue("iii", 123, 456, 789)      (123, 456, 789)
Py_BuildValue("s", "hello")              'hello'
Py_BuildValue("ss", "hello", "world")    ('hello', 'world')
Py_BuildValue("s#", "hello", 4)          'hell'
Py_BuildValue("()")                      ()
Py_BuildValue("(i)", 123)                (123,)
Py_BuildValue("(ii)", 123, 456)          (123, 456)
Py_BuildValue("(i,i)", 123, 456)         (123, 456)
Py_BuildValue("[i,i]", 123, 456)         [123, 456]
Py_BuildValue("{s:i,s:i}",
              "abc", 123, "def", 456)    {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)",
              1, 2, 3, 4, 5, 6)          (((1, 2), (3, 4)), (5, 6))

1.10. 参考计数

在像 C 或 C 这样的语言中,程序员负责堆上内存的动态分配和释放。在 C 语言中,这是使用malloc()free()函数完成的。在 C 语言中,运算符newdelete的使用具有相同的含义,我们将在下面的讨论中限制使用 C 语言。

分配给malloc()的每个内存块finally应pass一次对free()的调用返回到可用内存池。在正确的时间调用free()很重要。如果忘记了块的地址,但未调用free(),则直到程序终止,它所占用的内存才能被重用。这称为内存泄漏。另一方面,如果程序为一个块调用free(),然后 continue 使用该块,则它会pass另一个malloc()调用与重新使用该块产生冲突。这称为使用释放的内存。它具有与引用未初始化的数据相同的严重后果-核心转储,错误的结果,神秘的崩溃。

内存泄漏的常见原因是pass代码的异常路径。例如,一个函数可以分配一个内存块,进行一些计算,然后再次释放该块。现在,对Function要求的更改可能会向计算中添加测试,以检测错误情况并可能从Function中过早返回。采取这种过早退出的方式很容易忘记释放分配的内存块,尤其是在稍后将其添加到代码中时。此类泄漏一旦引入,通常很长一段时间都不会被发现:错误退出仅在所有调用中的一小部分中进行,并且大多数现代计算机都具有大量虚拟内存,因此该泄漏仅在长时间运行的过程中才会显现经常使用泄漏Function。因此,重要的是pass制定使这种错误最小化的编码约定或策略来防止泄漏的发生。

由于 Python 大量使用malloc()free(),因此需要一种避免内存泄漏以及释放内存的策略。选择的方法称为引用计数。原理很简单:每个对象都包含一个计数器,当对该对象的引用存储在某处时,该计数器将增加;而当删除对其的引用时,该计数器将减少。当计数器达到零时,该对象的最后一个引用已被删除,该对象被释放。

另一种策略称为自动垃圾收集。 (有时,引用计数也被称为垃圾收集策略,因此我使用“自动”来区分两者.)自动垃圾收集的一大优点是用户不需要显式调用free()。 (另一个声称的优点是速度或内存使用率的提高-但这并不是硬道理.)缺点是对于 C 语言,没有 true 可移植的自动垃圾收集器,而引用计数可移植地实现(只要这些Functionmalloc()free()可用-C 标准保证)。也许有一天,可以为 C 提供一个可移植性强的自动垃圾收集器。在那之前,我们将不得不使用引用计数。

虽然 Python 使用传统的参考计数实现,但它还提供了一个循环检测器,用于检测参考循环。这使应用程序不必担心创建直接或间接循环引用。这些是仅使用引用计数实现垃圾收集的弱点。引用循环由对象组成,这些对象包含(可能是间接的)对自身的引用,因此循环中的每个对象的引用计数都为非零。即使没有对循环本身的进一步引用,典型的引用计数实现也无法回收引用循环中属于任何对象的内存,或者无法从该循环中的对象引用的内存。

只要没有在 Python 中实现终结器(del()方法),循环检测器就可以检测垃圾循环并回收它们。当存在这样的终结器时,检测器将passgc模块(特别是该模块中的garbage变量)公开循环。 gc模块还提供了一种运行检测器的方式(collect()函数),以及配置接口和在运行时禁用检测器的Function。周期检测器被认为是可选组件;尽管默认情况下已将其包括在内,但可以在构建时使用 Unix 平台(包括 Mac OS X)上的 configure 脚本的--without-cycle-gc选项或pass删除其他平台上pyconfig.hHeaders 中的WITH_CYCLE_GC的定义来禁用它。如果以此方式禁用了循环检测器,则gc模块将不可用。

1.10.1. Python 中的引用计数

有两个宏Py_INCREF(x)Py_DECREF(x),它们处理参考计数的递增和递减。当计数达到零时,Py_DECREF()也会释放对象。为了灵活起见,它不会直接调用free(),而是pass对象的* type object *中的函数指针进行调用。为此(和其他)目的,每个对象还包含一个指向其类型对象的指针。

现在最大的问题仍然是:何时使用Py_INCREF(x)Py_DECREF(x)?让我们首先介绍一些术语。没有人“拥有”一个物体。但是,您可以拥有对对象的引用。现在,对象的引用计数定义为对该对象拥有的引用数。引用的所有者负责在不再需要引用时调用Py_DECREF()。引用的所有权可以转让。有三种处理拥有的引用的方式:传递,存储或调用Py_DECREF()。忘记处理拥有的引用会导致内存泄漏。

还可以“借用” [2]对对象的引用。参考的借阅者不应致电Py_DECREF()。借款人持有该物件的时间不得超过从其借款的所有人。在所有者处置完引用后再使用借用的引用可能会占用释放的内存,因此应完全避免[3]

借用而不是拥有引用的优点是,您无需在代码的所有可能路径上都处理引用,换句话说,借用引用不会在以下情况下冒泄漏的风险:过早退出。借用而不是拥有的缺点是,在一些微妙的情况下,在看似正确的代码中,借用的引用实际上可以在其被借用的所有者处置之后再使用。

可以pass调用Py_INCREF()将借用的引用更改为拥有的引用。这不会影响从中借用引用的所有者的状态,它会创建一个新的拥有的引用,并赋予所有者完全的责任(新所有者必须以及先前的所有者一样,正确处置该引用)。

1.10.2. 所有权规则

每当对象引用传入或传出函数时,所有权是否随引用一起转移都是函数接口规范的一部分。

返回对对象的引用的大多数函数都会对该引用进行所有权转让。特别是,要创建新对象的所有函数(例如PyInt_FromLong()Py_BuildValue())都会将所有权传递给接收者。即使对象不是 true 的新对象,您仍然会获得对该对象的新引用的所有权。例如,PyInt_FromLong()维护流行值的缓存,并且可以返回对缓存项的引用。

许多从其他对象中提取对象的函数也会pass引用转移所有权,例如PyObject_GetAttrString()。但是,这里的情况不太清楚,因为有一些常见的例程是 exception:PyTuple_GetItem()PyList_GetItem()PyDict_GetItem()PyDict_GetItemString()都返回您从 Tuples,列表或字典中借用的引用。

函数PyImport_AddModule()也返回借用的引用,即使它实际上可以创建它返回的对象:之所以可行,是因为对该对象的拥有的引用存储在sys.modules中。

通常,当您将对象引用传递给另一个函数时,该函数会从您那里借用该引用-如果需要存储该引用,它将使用Py_INCREF()成为独立所有者。该规则确实有两个重要 exception:PyTuple_SetItem()PyList_SetItem()。这些Function将接管传递给他们的物品的所有权-即使它们失败了! (请注意,PyDict_SetItem()和朋友不会接管所有权,他们是“正常的”。)

从 Python 调用 C 函数时,它从调用者那里借用了对其参数的引用。调用方拥有对该对象的引用,因此可以保证借用引用的生存期一直到该函数返回为止。仅在必须存储或传递此类借用的引用时,才必须pass调用Py_INCREF()将其转换为拥有的引用。

从 Python 调用的 C 函数返回的对象引用必须是拥有的引用—所有权从函数转移到其调用者。

1.10.3. 薄冰

在某些情况下,无害使用借用的参考文献可能会导致问题。所有这些都与解释器的隐式调用有关,这可能导致引用的所有者处置它。

要了解的第一个也是最重要的情况是在借用对列表项的引用时在不相关的对象上使用Py_DECREF()。例如:

void
bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    PyList_SetItem(list, 1, PyInt_FromLong(0L));
    PyObject_Print(item, stdout, 0); /* BUG! */
}

此函数首先借用对list[0]的引用,然后用值0替换list[1],最后打印借用的引用。看起来无害吧?但这不是!

让我们按照PyList_SetItem()的控制流程进行操作。该列表拥有对其所有项目的引用,因此,当替换项目 1 时,它必须处理原始项目 1.现在,假设原始项目 1 是用户定义的类的实例,并且进一步假设该类定义了del()方法。如果此类实例的引用计数为 1,则对其进行处置将调用其del()方法。

由于它是用 Python 编写的,因此del()方法可以执行任意 Python 代码。可能会做一些事情使bug()中对item的引用无效吗?你打赌!假设del()方法可以访问传递到bug()的列表,它可以执行del list[0]效果的语句,并假定这是对该对象的最后引用,它将释放与其关联的内存,从而使item无效。

一旦您知道问题的根源,解决方案就很容易:临时增加引用计数。该函数的正确版本为:

void
no_bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    Py_INCREF(item);
    PyList_SetItem(list, 1, PyInt_FromLong(0L));
    PyObject_Print(item, stdout, 0);
    Py_DECREF(item);
}

这是一个真实的故事。较旧的 Python 版本包含此错误的变体,并且有人在 C 调试器中花费了大量时间来弄清楚为什么他的del()方法会失败…

借用引用出现问题的第二种情况是涉及线程的变体。通常,Python 解释器中的多个线程不会互相干扰,因为存在一个全局锁来保护 Python 的整个对象空间。但是,可以使用宏Py_BEGIN_ALLOW_THREADS暂时释放此锁定,并使用Py_END_ALLOW_THREADS重新获取它。这在阻止 I/O 调用周围很普遍,以允许其他线程在 awaitI/O 完成时使用处理器。显然,以下函数与上一个函数具有相同的问题:

void
bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);
    Py_BEGIN_ALLOW_THREADS
    ...some blocking I/O call...
    Py_END_ALLOW_THREADS
    PyObject_Print(item, stdout, 0); /* BUG! */
}

1.10.4. 空指针

通常,将对象引用作为参数的函数不希望您传递它们* NULL 指针,如果这样做,它们将转储核心(或导致以后的核心转储)。返回对象引用的函数通常返回 NULL 仅表示发生异常。不测试 NULL 参数的原因是函数通常会将接收到的对象传递给其他函数-如果每个函数都要测试 NULL *,则会有很多冗余测试,并且代码运行速度会更慢。

当接收到可能是* NULL 的指针(例如,从malloc()或可能引发异常的函数)时,最好仅在“源:”处测试 NULL *。

Py_INCREF()Py_DECREF()不检查* NULL *指针,但是,它们的变体Py_XINCREF()Py_XDECREF()可以。

用于检查特定对象类型(Pytype_Check())的宏不会检查* NULL 指针-再次,有很多代码连续调用其中的几个以针对各种不同的预期类型测试对象,这会生成冗余测试。没有 NULL *检查的变体。

C 函数调用机制保证传递给 C 函数的参数列表(在示例中为args)从不为* NULL *-实际上,它保证始终为 Tuples[4]

曾经让* NULL *指针“转义”给 Python 用户是一个严重的错误。

1.11. 用 C 编写扩展

可以用 C 编写扩展模块。有一些限制。如果主程序(Python 解释器)由 C 编译器编译和链接,则不能使用具有构造函数的全局或静态对象。如果主程序由 C 编译器链接,则这不是问题。必须由extern "C"语句将由 Python 解释器调用的函数(特别是模块初始化函数)。无需将 Python 头文件包含在extern "C" {...}中-如果定义了符号__cplusplus(所有最新的 C 编译器都定义了此符号),它们已经使用了这种形式。

1.12. 为扩展模块提供 C API

许多扩展模块只是提供了要在 Python 中使用的新Function和类型,但是有时扩展模块中的代码对于其他扩展模块可能很有用。例如,扩展模块可以实现类型“ collection”,其类型类似于无 Sequences 的列表。就像标准的 Python 列表类型具有允许扩展模块创建和操作列表的 C API 一样,这种新的集合类型应具有一组 C 函数,可以从其他扩展模块直接进行操作。

乍一看,这似乎很容易:只需编写函数(当然,无需语句static),提供适当的头文件并记录 C API。实际上,如果所有扩展模块始终与 Python 解释器静态链接,那么这将起作用。但是,将模块用作共享库时,一个模块中定义的符号可能对另一模块不可见。可见性的详细信息取决于 os。有些系统为 Python 解释器和所有扩展模块使用一个全局名称空间(例如 Windows),而另一些系统则需要在模块链接时显式导入符号列表(AIX 是一个示例),或者提供不同策略的选择(大多数 Unices)。即使符号在全局可见,也可能尚未加载其要调用的Function的模块!

因此,可移植性不需要对符号可见性做任何假设。这意味着扩展模块中的所有符号都应语句为static,模块的初始化函数除外,以避免与其他扩展模块的名称冲突(如模块的方法表和初始化Function部分所述)。这意味着必须以其他方式导出可从其他扩展模块访问的符号。

Python 提供了一种特殊的机制,可以将 C 级信息(指针)从一个扩展模块传递到另一个扩展模块:胶囊。 Capsule 是一种 Python 数据类型,用于存储指针(void *)。只能pass其 C API 创建和访问胶囊,但是它们可以像其他任何 Python 对象一样传递。特别是,可以将它们分配给扩展模块名称空间中的名称。然后,其他扩展模块可以导入此模块,检索此名称的值,然后从 Capsule 中检索指针。

pass多种方式可以使用 Capsules 导出扩展模块的 C API。每个函数都可以拥有自己的 Capsule,或者所有 C API 指针都可以存储在地址发布在 Capsule 中的数组中。并且,可以在提供代码的模块和 Client 端模块之间以不同的方式分配存储和检索指针的各种任务。

无论选择哪种方法,正确命名您的胶囊都是很重要的。函数PyCapsule_New()采用名称参数(const char *);您可以 Importing* NULL *名称,但我们强烈建议您指定一个名称。正确命名的 Capsules 提供一定程度的运行时类型安全性;没有可行的方法将一个未命名的胶囊与另一个胶囊区分开。

特别是,用于公开 C API 的胶囊应遵循以下约定命名:

modulename.attributename

便利FunctionPyCapsule_Import()使加载 Capsule 提供的 C API 变得容易,但前提是 Capsule 的名称与此约定匹配。此行为使 C API 用户高度确定他们加载的 Capsule 包含正确的 C API。

下面的示例演示了一种方法,该方法将大部分负担都放在了导出模块的编写者身上,这适用于常用的库模块。它将所有 C API 指针(在示例中仅一个!)存储在void指针数组中,该数组成为 Capsule 的值。与模块相对应的头文件提供了一个宏,该宏负责导入模块并检索其 C API 指针。Client 端模块仅需在访问 C API 之前调用此宏。

导出模块是对一个简单的例子部分中spam模块的修改。函数spam.system()并不直接调用 C 库函数system(),而是函数PySpam_System(),它实际上在实际情况中会做一些更复杂的事情(例如在每个命令中添加“垃圾邮件”)。此FunctionPySpam_System()也已导出到其他扩展模块。

函数PySpam_System()是普通的 C 函数,像其他所有函数一样语句为static

static int
PySpam_System(const char *command)
{
    return system(command);
}

函数spam_system()的修改很简单:

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = PySpam_System(command);
    return Py_BuildValue("i", sts);
}

在模块的开头,在该行之后

#include "Python.h"

必须再添加两行:

#define SPAM_MODULE
#include "spammodule.h"

#define用于告诉头文件它已包含在导出模块中,而不是 Client 端模块中。最后,模块的初始化函数必须负责初始化 C API 指针数组:

PyMODINIT_FUNC
initspam(void)
{
    PyObject *m;
    static void *PySpam_API[PySpam_API_pointers];
    PyObject *c_api_object;

    m = Py_InitModule("spam", SpamMethods);
    if (m == NULL)
        return;

    /* Initialize the C API pointer array */
    PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;

    /* Create a Capsule containing the API pointer array's address */
    c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);

    if (c_api_object != NULL)
        PyModule_AddObject(m, "_C_API", c_api_object);
}

注意PySpam_API被语句为static;否则当initspam()终止时,指针数组将消失!

大部分工作在头文件spammodule.h中,如下所示:

#ifndef Py_SPAMMODULE_H
#define Py_SPAMMODULE_H
#ifdef __cplusplus
extern "C" {
#endif

/* Header file for spammodule */

/* C API functions */
#define PySpam_System_NUM 0
#define PySpam_System_RETURN int
#define PySpam_System_PROTO (const char *command)

/* Total number of C API pointers */
#define PySpam_API_pointers 1

#ifdef SPAM_MODULE
/* This section is used when compiling spammodule.c */

static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;

#else
/* This section is used in modules that use spammodule's API */

static void **PySpam_API;

#define PySpam_System \
 (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])

/* Return -1 on error, 0 on success.
 * PyCapsule_Import will set an exception if there's an error.
 */
static int
import_spam(void)
{
    PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0);
    return (PySpam_API != NULL) ? 0 : -1;
}

#endif

#ifdef __cplusplus
}
#endif

#endif /* !defined(Py_SPAMMODULE_H) */

Client 端模块为了访问FunctionPySpam_System()所必须要做的就是在其初始化函数中调用函数import_spam()

PyMODINIT_FUNC
initclient(void)
{
    PyObject *m;

    m = Py_InitModule("client", ClientMethods);
    if (m == NULL)
        return;
    if (import_spam() < 0)
        return;
    /* additional initialization can happen here */
}

这种方法的主要缺点是文件spammodule.h相当复杂。但是,导出的每个函数的基本结构都相同,因此仅需学习一次。

最后应该提到的是,Capsules 提供了其他Function,这对于在 Capsule 中存储的指针进行内存分配和释放特别有用。有关详细信息,请参见《 Python/C API 参考手册》的Capsules部分以及 Capsules(Python 源代码分发中的文件Include/pycapsule.hObjects/pycapsule.c)的实现。

Footnotes

  • [1]

    • 标准模块os中已经存在用于此Function的接口,它被选为简单直接的示例。
  • [2]

    • “借用”参考文献的隐喻并不完全正确:所有者仍然拥有参考文献的副本。
  • [3]

    • 检查引用计数至少为 1 不起作用 –引用计数本身可能在释放的内存中,因此可以重用于另一个对象!
  • [4]

    • 当您使用“旧”样式的调用约定时,这些保证不成立-在许多现有代码中仍然可以找到。