1. 用C或C++扩展Python

如果您知道如何用C语言编程,那么向Python中添加新的内置模块是非常容易的。 extension modules 可以做两件不能在Python中直接完成的事情:它们可以实现新的内置对象类型,并且可以调用C库函数和系统调用。

为了支持扩展,python api(application programmers interface)定义了一组函数、宏和变量,这些函数、宏和变量提供了对python运行时系统大部分方面的访问。python api通过包含头文件合并到C源文件中。 "Python.h" .

扩展模块的编译取决于它的预期用途以及您的系统设置;详细信息将在后面的章节中给出。

注解

C扩展接口是特定于c python的,扩展模块不在其他Python实现上工作。在许多情况下,可以避免编写C扩展并保留到其他实现的可移植性。例如,如果您的用例正在调用C库函数或系统调用,那么您应该考虑使用 ctypes 模块或 cffi 库而不是编写自定义C代码。这些模块允许您编写python代码以与C代码接口,并且比编写和编译C扩展模块更易于在python实现之间移植。

1.1. 一个简单的例子

让我们创建一个名为 spam (Monty python粉丝最类似于的食物…)假设我们想创建一个C库函数的python接口 system() 1. 此函数接受以空结尾的字符串作为参数,并返回一个整数。我们希望这个函数可以从python调用,如下所示:

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

首先创建一个文件 spammodule.c . (历史上,如果调用模块 spam ,将调用包含其实现的C文件 spammodule.c ;如果模块名很长,例如 spammify ,模块名可以是 spammify.c

我们文件的前两行可以是:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

它引入了python api(您可以添加一个描述模块用途的注释,如果愿意,还可以添加一个版权声明)。

注解

因为python可能定义一些影响某些系统上标准头的预处理器定义,所以 must 包括 Python.h 在包含任何标准标题之前。

PY_SSIZE_T_CLEAN 包括之前 Python.h . 见 在扩展函数中提取参数

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

接下来我们要添加到模块文件中的是C函数,当python表达式 spam.system(string) 被评估(稍后我们将看到它是如何被调用的)::

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 PyLong_FromLong(sts);
}

python中的参数列表有一个简单的转换(例如,单个表达式 "ls -l" )传递给C函数的参数。c函数总是有两个参数,按惯例命名 selfargs .

这个 self 参数指向模块级函数的模块对象;对于方法,参数指向对象实例。

这个 args 参数将是指向包含参数的python tuple对象的指针。元组的每个项都对应于调用的参数列表中的一个参数。参数是python对象——为了在C函数中处理它们,我们必须将它们转换为C值。函数 PyArg_ParseTuple() 在python API中,检查参数类型并将其转换为C值。它使用模板字符串来确定所需的参数类型以及存储转换值的C变量类型。稍后再详细介绍。

PyArg_ParseTuple() 如果所有参数的类型都正确,并且其组件存储在传递地址的变量中,则返回true(非零)。如果传递的参数列表无效,则返回false(零)。在后一种情况下,它还会引发适当的异常,以便调用函数可以返回 NULL 立即(正如我们在示例中看到的那样)。

1.2. Intermezzo:错误和异常

整个Python解释器中的一个重要约定是:当一个函数失败时,它应该设置一个异常条件并返回一个错误值(通常是 NULL 指针)。异常存储在解释器内的静态全局变量中;如果该变量是 NULL 未发生异常。第二个全局变量存储异常的“关联值”(第二个参数 raise )第三个变量包含堆栈回溯,以防错误源于python代码。这三个变量是python中结果的c等价物。 sys.exc_info() (参见模块部分 sys 在python库参考中)。了解错误是如何传递的,这一点很重要。

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

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

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

可以非破坏性地测试是否使用 PyErr_Occurred() . 这将返回当前的异常对象,或者 NULL 如果没有发生异常。你通常不需要打电话 PyErr_Occurred() 查看函数调用中是否发生错误,因为您应该能够从返回值中分辨出来。

当一个函数 f 调用另一个函数 g 检测到后者失败, f 是否应返回错误值(通常 NULL-1 )。它应该 not 调用其中之一 PyErr_* 函数---已被调用 g . f 的调用方还应返回错误指示 its 再次调用 没有 打电话 PyErr_* 等等---错误的最详细原因已经由第一个检测到它的函数报告了。一旦错误到达python解释器的主循环,这将中止当前正在执行的python代码,并尝试查找由python程序员指定的异常处理程序。

(有些情况下,一个模块可以通过调用另一个模块来实际给出更详细的错误消息 PyErr_* 在这种情况下,可以这样做。但是,作为一般规则,这是不必要的,并且可能导致有关错误原因的信息丢失:大多数操作可能由于各种原因而失败。)

要忽略由失败的函数调用设置的异常,必须通过调用显式清除异常条件 PyErr_Clear() . 唯一一次C代码应该调用 PyErr_Clear() 如果它不想把错误传递给解释器,而是想完全由它自己来处理(可能是通过尝试其他方法,或者假装什么都没有出错)。

每一次失败 malloc() 调用必须转换为异常---的直接调用方 malloc() (或) realloc() )必须调用 PyErr_NoMemory() 并返回故障指示器本身。所有对象创建函数(例如, PyLong_FromLong() )已经这样做了,所以这个注释只与调用的人有关 malloc() 直接。

还要注意的是,除了 PyArg_ParseTuple() 朋友们,返回整型状态的函数通常返回一个正值或零,以表示成功和 -1 对于失败,比如Unix系统调用。

最后,小心清理垃圾(通过 Py_XDECREF()Py_DECREF() 调用已创建的对象)返回错误指示器时!

选择哪一个例外完全是你的。存在对应于所有内置python异常的预声明C对象,例如 PyExc_ZeroDivisionError ,可以直接使用。当然,你应该明智地选择例外——不要使用 PyExc_TypeError 意味着无法打开文件(可能 PyExc_IOError )如果参数列表有问题, PyArg_ParseTuple() 函数通常引发 PyExc_TypeError . 如果有一个参数的值必须在特定范围内或必须满足其他条件, PyExc_ValueError 是合适的。

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

static PyObject *SpamError;

并在模块的初始化函数中初始化它 (PyInit_spam() )有一个异常对象:

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

注意,异常对象的python名称是 spam.error . 这个 PyErr_NewException() 函数可以创建一个基类为 Exception (除非传入另一个类而不是 NULL ),描述在 内置异常 .

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

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

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

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;

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

下一个语句是对unix函数的调用 system() 把我们刚得到的字符串传给它 PyArg_ParseTuple() ::

sts = system(command);

我们的 spam.system() 函数必须返回的值 sts 作为python对象。这是使用函数完成的 PyLong_FromLong() . ::

return PyLong_FromLong(sts);

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

如果有一个C函数不返回有用的参数(返回 void )对应的python函数必须返回 None . 您需要使用这个成语来完成此操作(它由 Py_RETURN_NONE 宏):

Py_INCREF(Py_None);
return Py_None;

Py_None 是特殊python对象的C名称 None .它是一个真正的python对象,而不是 NULL 指针,这在大多数上下文中意味着“错误”,如我们所见。

1.4. 模块方法表及初始化功能

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

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

注意第三项 (METH_VARARGS )这是一个标志,告诉解释器将用于C函数的调用约定。通常应该是 METH_VARARGSMETH_VARARGS | METH_KEYWORDS 一个值 0 意味着 PyArg_ParseTuple() 使用。

仅在使用时 METH_VARARGS ,函数应该期望将python级别的参数作为一个元组传入,以便通过 PyArg_ParseTuple() ;下面提供了有关此功能的更多信息。

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

方法表必须在模块定义结构中引用::

static struct PyModuleDef spammodule = {
    PyModuleDef_HEAD_INIT,
    "spam",   /* name of module */
    spam_doc, /* module documentation, may be NULL */
    -1,       /* size of per-interpreter state of the module,
                 or -1 if the module keeps state in global variables. */
    SpamMethods
};

反过来,这个结构必须传递给模块初始化函数中的解释器。初始化函数必须命名 PyInit_name() 在哪里 name 是模块的名称,并且应该是唯一的非-static 模块文件中定义的项::

PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModule_Create(&spammodule);
}

注意pymodinit_func声明函数为 PyObject * 返回类型,声明平台所需的任何特殊链接声明,而C++则声明该函数为 extern "C" .

当python程序导入模块时 spam 第一次, PyInit_spam() 被称为。(有关嵌入python的注释,请参见下文。)它调用 PyModule_Create() ,它返回一个模块对象,并将内置函数对象插入到基于表(一个 PyMethodDef 结构)在模块定义中找到。 PyModule_Create() 返回指向其创建的模块对象的指针。对于某些错误,它可能会以致命错误中止,或者返回 NULL 如果模块不能令人满意地初始化。init函数必须将module对象返回给它的调用者,这样它就可以被插入到 sys.modules .

在嵌入python时, PyInit_spam() 除非在 PyImport_Inittab 表。要将模块添加到初始化表中,请使用 PyImport_AppendInittab() ,后面可选导入模块::

int
main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    if (program == NULL) {
        fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
        exit(1);
    }

    /* Add a built-in module, before Py_Initialize */
    if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {
        fprintf(stderr, "Error: could not extend in-built modules table\n");
        exit(1);
    }

    /* Pass argv[0] to the Python interpreter */
    Py_SetProgramName(program);

    /* Initialize the Python interpreter.  Required.
       If this step fails, it will be a fatal error. */
    Py_Initialize();

    /* Optionally import the module; alternatively,
       import can be deferred until the embedded script
       imports it. */
    pmodule = PyImport_ImportModule("spam");
    if (!pmodule) {
        PyErr_Print();
        fprintf(stderr, "Error: could not import module 'spam'\n");
    }

    ...

    PyMem_RawFree(program);
    return 0;
}

注解

正在从中删除条目 sys.modules 或将编译后的模块导入进程内的多个解释程序(或 fork() 没有干预 exec() )可能会给某些扩展模块带来问题。在初始化内部数据结构时,扩展模块作者应该谨慎。

python源代码发行版中包含了一个更为重要的示例模块 Modules/xxmodule.c . 此文件可以用作模板,也可以简单地作为示例读取。

注解

不像我们 spam 例子, xxmodule 使用 multi-phase initialization (在python 3.5中是新的),其中pymoduledef结构从 PyInit_spam ,模块的创建留给导入机制。有关多阶段初始化的详细信息,请参见 PEP 489 .

1.5. 汇编与链接

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

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

spam spammodule.o

并通过运行重新生成解释器 make 在顶层目录中。你也可以运行 makeModules/ 子目录,但必须先重新生成 Makefile 通过运行:程序:make`makefile'。(每次更改 :file:`Setup 文件。

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

spam spammodule.o -lX11

1.6. 从C调用python函数

到目前为止,我们主要致力于使C函数可以从Python调用。反过来也很有用:从C调用python函数。这对于支持所谓的“回调”函数的库尤其有用。如果C接口使用回调,则等效的python通常需要向python程序员提供回调机制;实现将需要从C回调调用python回调函数。其他用途也是可以想象的。

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

调用python函数很容易。首先,python程序必须以某种方式将python函数对象传递给您。您应该提供一个函数(或其他接口)来完成这项工作。调用此函数时,请保存指向python函数对象的指针(注意 Py_INCREF() 它!)在一个全局变量中——或者任何你认为合适的地方。例如,以下函数可能是模块定义的一部分:

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;
}

此函数必须使用 METH_VARARGS 标记;这在第节中描述 模块方法表及初始化功能 . 这个 PyArg_ParseTuple() 函数及其参数记录在第节中。 在扩展函数中提取参数 .

宏指令 Py_XINCREF()Py_XDECREF() 增加/减少对象的引用计数,并且在存在 NULL 指针(但请注意 temp 不会 NULL 在这种情况下)。更多信息请参见第节。 参考计数 .

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

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() 就其参数而言,是“引用计数中性”。在这个例子中,创建了一个新的元组作为参数列表,即 Py_DECREF() -Ed紧接着 PyObject_CallObject() 调用。

的返回值 PyObject_CallObject() 是“新的”:要么是一个全新的对象,要么是一个引用计数已增加的现有对象。所以,除非你想把它保存在一个全局变量中,否则你应该 Py_DECREF() 结果,甚至(特别是!)如果你对它的价值不感兴趣。

但是,在执行此操作之前,必须检查返回值是否 NULL . 如果是,则python函数将通过引发异常终止。如果调用的C代码 PyObject_CallObject() 是从python调用的,它现在应该向其python调用方返回一个错误指示,以便解释器可以打印堆栈跟踪,或者调用python代码可以处理异常。如果这不可能或不可取,则应通过调用清除异常 PyErr_Clear() . 例如::

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

根据所需的python回调函数接口,您可能还需要提供参数列表 PyObject_CallObject() . 在某些情况下,参数列表也由python程序通过指定回调函数的相同接口提供。然后可以以与函数对象相同的方式保存和使用它。在其他情况下,可能需要构造一个新的元组作为参数列表传递。最简单的方法是调用 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, const char *format, ...);

这个 arg 参数必须是包含从python传递到c函数的参数列表的元组对象。这个 格式 参数必须是格式字符串,其语法在 分析参数并生成值 在python/c API参考手册中。其余参数必须是其类型由格式字符串确定的变量的地址。

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

注意,提供给调用者的任何python对象引用都是 借来 引用;不要减少它们的引用计数!

一些示例调用:

#define PY_SSIZE_T_CLEAN  /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>
int ok;
int i, j;
long k, l;
const char *s;
Py_ssize_t 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. 扩展函数的关键字参数

这个 PyArg_ParseTupleAndKeywords() 函数声明如下:

int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
                                const char *format, char *kwlist[], ...);

这个 arg格式 参数与 PyArg_ParseTuple() 功能。这个 克威特 参数是作为第三个参数从python运行时接收的关键字字典。这个 KWIST 参数是 NULL -用于标识参数的字符串的终止列表;名称与中的类型信息匹配 格式 从左到右。关于成功, PyArg_ParseTupleAndKeywords() 返回true,否则返回false并引发适当的异常。

注解

使用关键字参数时无法分析嵌套元组!传入的关键字参数中不存在 KWIST 将导致 TypeError 被引发。

下面是一个使用关键字的示例模块,基于geoff philbrick(philbrick@hks.com)的示例:

#define PY_SSIZE_T_CLEAN  /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>

static PyObject *
keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
{
    int voltage;
    const char *state = "a stiff";
    const char *action = "voom";
    const 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_RETURN_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)(void(*)(void))keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
     "Print a lovely skit to standard output."},
    {NULL, NULL, 0, NULL}   /* sentinel */
};

static struct PyModuleDef keywdargmodule = {
    PyModuleDef_HEAD_INIT,
    "keywdarg",
    NULL,
    -1,
    keywdarg_methods
};

PyMODINIT_FUNC
PyInit_keywdarg(void)
{
    return PyModule_Create(&keywdargmodule);
}

1.9. 建立任意值

这个功能与 PyArg_ParseTuple() . 声明如下:

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

它识别的一组格式单位与 PyArg_ParseTuple() ,但参数(是函数的输入,而不是输出)不能是指针,只能是值。它返回一个新的python对象,适用于从从python调用的C函数返回。

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

示例(左边是调用,右边是生成的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("y", "hello")              b'hello'
Py_BuildValue("ss", "hello", "world")    ('hello', 'world')
Py_BuildValue("s#", "hello", 4)          'hell'
Py_BuildValue("y#", "hello", 4)          b'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() 最终只需调用一次 free() .调用很重要 free() 在正确的时间。如果一个街区的地址被遗忘了,但是 free() 不调用它,它所占用的内存在程序终止之前不能重用。这叫A memory leak .另一方面,如果程序调用 free() 对于一个块,然后继续使用该块,它会与通过另一个块重新使用该块产生冲突 malloc() 调用。这叫做 using freed memory . 它与引用未初始化的数据具有相同的不良后果:核心转储、错误的结果、神秘的崩溃。

内存泄漏的常见原因是通过代码的异常路径。例如,一个函数可以分配一个内存块,进行一些计算,然后再次释放该块。现在,函数需求的变化可能会给计算添加一个测试,以检测错误条件,并可能过早地从函数返回。在过早退出时,很容易忘记释放分配的内存块,特别是在稍后将其添加到代码中时。这种泄漏一旦被引入,往往会在很长一段时间内被检测不到:错误退出只在所有调用的一小部分中进行,而且大多数现代机器都有大量的虚拟内存,因此泄漏只在经常使用泄漏函数的长时间运行过程中变得明显。因此,有一个编码约定或策略来最小化这种错误,以防止泄漏发生是很重要的。

因为python大量使用 malloc()free() 它需要一种策略来避免内存泄漏以及使用释放的内存。调用所选方法 reference counting . 原理很简单:每个对象都包含一个计数器,当对该对象的引用存储在某个位置时,计数器递增;当对该对象的引用被删除时,计数器递减。当计数器达到零时,对对象的最后一个引用已被删除,对象将被释放。

另一种策略被称为 automatic garbage collection . (有时,引用计数也被称为垃圾收集策略,因此我使用“自动”来区分这两者。)自动垃圾收集的最大优点是用户不需要调用 free() 明确地。(另一个声称的优点是提高了速度或内存使用率,但这并不困难。)缺点是对于C来说,没有真正可移植的自动垃圾收集器,而引用计数可以移植地实现(只要函数 malloc()free() 可供选择---C标准保证)。也许有一天,C可以使用一个足够便携的自动垃圾收集器。在那之前,我们必须使用参考计数。

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

循环检测器能够检测垃圾循环并回收它们。这个 gc 模块公开了一种运行探测器的方法 collect() 功能),以及配置接口和在运行时禁用探测器的能力。循环检测器被视为可选组件;尽管默认情况下包含它,但可以在构建时使用 --without-cycle-gc 选择权 configure UNIX平台(包括Mac OS X)上的脚本。如果循环检测器以这种方式禁用,则 gc 模块将不可用。

1.10.1. python中的引用计数

有两个宏, Py_INCREF(x)Py_DECREF(x) ,它处理引用计数的递增和递减。 Py_DECREF() 当计数为零时也释放对象。为了灵活性,它不需要 free() 直接——相反,它通过对象中的函数指针进行调用 type object . 为此目的(以及其他目的),每个对象还包含指向其类型对象的指针。

现在最大的问题是:何时使用 Py_INCREF(x)Py_DECREF(x) ?让我们先介绍一些术语。没有人“拥有”物品;但是,你可以 own a reference 对一个物体。对象的引用计数现在定义为其拥有的引用数。样板客户的所有者负责 Py_DECREF() 当不再需要参考时。引用的所有权可以转移。处理拥有的引用有三种方法:传递、存储或调用 Py_DECREF() . 忘记处理所拥有的引用会导致内存泄漏。

也有可能 borrow 2 对对象的引用。举荐人不得致电 Py_DECREF() . 借款人对该标的物的持有时间不得超过该标的物的所有人。在所有者处理完它之后使用借用的引用会有使用释放内存的风险,应该完全避免使用 3.

与拥有引用相比,借用的好处在于,您不需要在代码的所有可能路径上处理引用——换句话说,使用借用的引用,您不会在过早退出时冒泄漏的风险。借阅而不是拥有的缺点是,在一些微妙的情况下,在看似正确的代码中,借阅的引用可以在借阅它的所有者实际上已经处理它之后使用。

通过调用 Py_INCREF() . 这不会影响从中借用引用的所有者的状态---它创建一个新的所有者引用,并赋予所有者全部责任(新所有者必须正确处理引用,以及前所有者)。

1.10.2. 所有权规则

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

大多数返回对对象引用的函数都通过引用传递所有权。特别是,其功能是创建新对象的所有函数,例如 PyLong_FromLong()Py_BuildValue() 将所有权传递给接收者。即使对象实际上不是新的,您仍然可以获得对该对象的新引用的所有权。例如, PyLong_FromLong() 维护常用值的缓存,并可以返回对缓存项的引用。

例如,许多从其他对象中提取对象的函数还通过引用传递所有权。 PyObject_GetAttrString() . 然而,这里的情况不太清楚,因为一些常见的例行程序是例外: PyTuple_GetItem()PyList_GetItem()PyDict_GetItem()PyDict_GetItemString() 所有返回从元组、列表或字典中借用的引用。

函数 PyImport_AddModule() 同时返回一个借用的引用,即使它可能实际创建它返回的对象:这是可能的,因为对该对象的拥有的引用存储在 sys.modules .

当您将一个对象引用传递给另一个函数时,通常,该函数从您那里借用引用——如果需要存储它,它将使用 Py_INCREF() 成为独立所有人。这条规则有两个重要的例外: PyTuple_SetItem()PyList_SetItem() .这些函数接管传递给它们的项目的所有权——即使它们失败了!(注意 PyDict_SetItem() 而朋友不会接管所有权——他们是“正常的”。)

当从Python调用C函数时,它从调用者那里借用对其参数的引用。调用方拥有对该对象的引用,因此在函数返回之前,所借用引用的生存期是有保证的。只有在必须存储或传递此类借用的引用时,才能通过调用将其转换为拥有的引用 Py_INCREF() .

从从python调用的C函数返回的对象引用必须是一个拥有的引用---所有权从该函数转移到其调用方。

1.10.3. 薄冰

在一些情况下,使用借来的参考资料看起来无害,可能会导致问题。这些都与解释程序的隐式调用有关,这可能导致引用的所有者对其进行处理。

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

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

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

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

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

因为它是用python编写的, __del__() 方法可以执行任意的python代码。它是否可以做些什么使引用无效? item 在里面 bug() ?当然!假设列表传递到 bug() 可以访问 __del__() 方法,它可以执行语句 del list[0] ,并且假设这是对该对象的最后一次引用,它将释放与其关联的内存,从而使 item .

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

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

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

这是一个真实的故事。一个旧版本的python包含了这个bug的变体,有人花了相当长的时间在C调试器中,以找出为什么 __del__() 方法将失败…

借用引用的第二个问题是涉及线程的变体。通常,Python解释器中的多个线程不能互相妨碍,因为有一个全局锁保护Python的整个对象空间。但是,可以使用宏临时释放此锁 Py_BEGIN_ALLOW_THREADS ,并使用 Py_END_ALLOW_THREADS . 这在阻止I/O调用方面很常见,以便让其他线程在等待I/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 只有在“源:”时,指针可能 NULL 例如,从 malloc() 或者来自可能引发异常的函数。

宏指令 Py_INCREF()Py_DECREF() 不要检查 NULL 指针——然而,它们的变体 Py_XINCREF()Py_XDECREF() 做。

用于检查特定对象类型的宏 (Pytype_Check() )不检查 NULL 指针——同样,有许多代码在一行中调用其中的几个来针对不同的预期类型测试对象,这将生成冗余测试。没有与 NULL 检查。

C函数调用机制保证传递给C函数的参数列表 (args 在例子中)永远不会 NULL ——事实上,它保证它总是一个元组。 4.

这是一个严重的错误 NULL 指向python用户的指针“escape”。

1.11. C++中的扩展扩展

用C++编写扩展模块是可能的。有些限制适用。如果主程序(Python解释器)是由C编译器编译和链接的,则不能使用带有构造函数的全局或静态对象。如果主程序是由C++编译器连接的,这不是问题。必须使用 extern "C" . 不需要将python头文件放在 extern "C" {{...}} ---如果符号 __cplusplus 定义(所有最近的C++编译器定义这个符号)。

1.12. 为扩展模块提供C API

许多扩展模块只是提供了从Python使用的新函数和类型,但有时扩展模块中的代码对其他扩展模块很有用。例如,扩展模块可以实现一个类型“collection”,它的工作方式类似于没有顺序的列表。就像标准的python列表类型有一个C API,它允许扩展模块创建和操作列表一样,这个新的集合类型应该有一组C函数,用于直接从其他扩展模块进行操作。

乍一看,这似乎很简单:只需编写函数(而不声明它们 static 当然),提供适当的头文件,并记录C API。事实上,如果所有扩展模块总是静态地与Python解释器链接,这就可以工作。但是,当模块用作共享库时,一个模块中定义的符号可能对另一个模块不可见。可见性的细节取决于操作系统;一些系统使用一个全局命名空间作为Python解释器和所有扩展模块(例如,Windows),而其他系统则需要在模块链接时显式地列出导入的符号(例如,AIX),或者提供不同策略的选择(大多数unice)。即使符号是全局可见的,但希望调用其函数的模块可能尚未加载!

因此,可移植性要求不要对符号可见性做任何假设。这意味着扩展模块中的所有符号都应该声明 static ,除了模块的初始化功能外,为了避免与其他扩展模块的名称冲突(如第节所述 模块方法表及初始化功能 )这意味着 应该 从其他扩展模块访问必须以不同的方式导出。

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

有许多方法可以使用Capsules导出扩展模块的C API。每个函数都可以获得自己的Capsules,或者所有C API指针都可以存储在一个数组中,该数组的地址在Capsules中发布。存储和检索指针的各种任务可以在提供代码的模块和客户机模块之间以不同的方式分布。

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

特别是,用于暴露C API的Capsules应按照本惯例命名:

modulename.attributename

便利功能 PyCapsule_Import() 使加载通过Capsules提供的C API变得容易,但前提是Capsules的名称与此约定匹配。这种行为使C API用户高度确信他们装载的Capsules包含正确的C API。

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

导出模块是对 spam 节中的模块 一个简单的例子 .功能 spam.system() 不调用C库函数 system() 直接,但功能 PySpam_System() 当然,这会在现实中做一些更复杂的事情(例如为每个命令添加“spam”)。这个函数 PySpam_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 PyLong_FromLong(sts);
}

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

#include <Python.h>

必须再添加两行:

#define SPAM_MODULE
#include "spammodule.h"

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

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

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    /* 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 (PyModule_AddObject(m, "_C_API", c_api_object) < 0) {
        Py_XDECREF(c_api_object);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

注意 PySpam_API 被声明 static ;否则指针数组将在 PyInit_spam() 终止!

大部分工作都在头文件中 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) */

客户机模块为访问该函数必须执行的所有操作 PySpam_System() 是调用函数(或者更确切地说是宏) import_spam() 在其初始化函数中:

PyMODINIT_FUNC
PyInit_client(void)
{
    PyObject *m;

    m = PyModule_Create(&clientmodule);
    if (m == NULL)
        return NULL;
    if (import_spam() < 0)
        return NULL;
    /* additional initialization can happen here */
    return m;
}

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

最后应该提到的是,Capsules提供了额外的功能,这对于存储在Capsules中的指针的内存分配和释放特别有用。在本节的python/c api参考手册中描述了详细信息。 Capsules 以及Capsules(文件)的实施 Include/pycapsule.hObjects/pycapsule.c 在python源代码分发中)。

脚注

1

标准模块中已存在此函数的接口 os ——这是一个简单明了的例子。

2

“借用”引用的比喻不完全正确:所有者仍然拥有引用的副本。

3

正在检查引用计数是否至少为1 不起作用 ---引用计数本身可以在释放的内存中,因此可以重新用于其他对象!

4

当您使用“旧”风格的调用约定时,这些保证是不成立的——这在许多现有的代码中仍然存在。