1. Extending Python with C or C++¶
如果你知道如何在C中编程,那么很容易将新的内建模块添加到Python中。这样的扩展模块可以做两件事情,这些都不能直接在Python中完成:他们可以实现新的内建对象类型,它们可以调用C库函数和系统调用。
为了支持扩展,Python API(应用编程接口)定义了一组函数,宏和变量,它们可以访问Python运行期系统的大部分方面。通过包含头文件"Python.h"
,这些Python API可以在C源文件中使用。
扩展模块的编译取决于其预期用途以及系统设置;详情在后面的章节中给出。
注意
C扩展接口特定于CPython,扩展模块不适用于其他Python实现。在许多情况下,可以避免编写C扩展并保持对其他实现的可移植性。例如,如果您的用例调用C库函数或系统调用,则应考虑使用ctypes
模块或cffi库,而不是编写自定义C代码。这些模块允许您以更具有可移植性的方式编写Python代码与C代码与Python的解释器实现接口,而不是编写和编译C扩展模块。
1.1. A Simple Example¶
我们来创建一个名为spam
的扩展模块(spam,monty python粉丝们最喜欢的食物),我们想给C库函数system()
创建一个Python的接口。[1] 这个函数以一个空字符结尾的字符串为参数,返回一个整数。我们希望在Python中这样调用这个函数:
>>> import spam
>>> status = spam.system("ls -l")
首先创建名为spammodule.c
的文件。(按历史惯例,如果模块叫做spam
,那么包含实现的C文件就叫做spammodule.c
;如果模块的名字很长,比如spammify
,那么模块的名字可以是spammify.c
。)
文件的第一行为:
#include <Python.h>
这会引入Python API的声明(如果乐意,你可以加上关于模块的说明和版权信息的注释)。
注意
在某些平台上,Python可能会定义一些预处理器定义,从而影响到标准头文件,所以在包含任何的标准头文件之前必须先包含Python.h
。
除了在标准头文件中定义的符号外,Python.h
中定义的用户可见的符号以PY
或者Py
打头。为了方便起见,由于它们被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函数总是有两个参数,通常命名为self和args。
self参数指向模块级函数的模块对象;对于一个方法,它将指向对象实例。
args参数是一个指向包含参数的Python元组对象的指针。元组中的每一个元素对应调用参数列表中的一个参数。这些参数是Python对象——为了能在C函数中处理这些Python对象,我们需要将其转成C可操作的值。Python API中的PyArg_ParseTuple()
函数检查参数类型,并将其转成对应的C值。它使用模板字符串来决定所需要的参数类型,同时也决定用来存转换后的值的C变量的类型。后面有更多的说明。
如果所有的参数有正确的类型,且值被存入变量(通过传地址)PyArg_ParseTuple()
返回真(非0值)。如果传递了不正确的参数,它返回假(0值)。对于后一种情况,它还会抛出异常,所以函数立即返回NULL(如同例子所示)。
1.2. Intermezzo:错误和异常¶
贯穿Python解释器的一个重要惯例如下:如果一个函数失败,它应该设置一个异常条件并返回一个错误值(一般来说是个NULL指针)。异常存储在解释器内的静态全局变量中;如果此变量为NULL,则没有发生异常。第二个全局变量保存异常“所关联的值”(raise
的第二个参数)。如果Python代码中发生了错误,第三个变量包含了函数调用栈。These three variables are the C equivalents of the result in Python of sys.exc_info()
(see the section on module sys
in the Python Library Reference). 了解这三个变量有助于理解错误是如何传递的。
Python API定义了一组函数来设置各种异常。
最常用的一个函数是PyErr_SetString()
。它的参数是一个异常对象和一个C字符串。异常对象一般是个预定义的对象,比如PyExc_ZeroDivisionError
。C字符串表明了错误的原因,它被转成Python字符串对象,并被当成异常“所关联的值”而保存。
另一个有用的函数是PyErr_SetFromErrno()
它只有一个异常参数,它通过检查全局变量errno
来构造异常值。最通用的函数是PyErr_SetObject()
它有两个对象参数,异常和它的值。对于传递给这些函数的对象,你不需要调用Py_INCREF()
(增加引用计数)。
你可以通过调用PyErr_Occurred()
来非破坏性地检查是否有无异常。它返回当前的异常对象,或者如果没有异常发生,它返回NULL。一般来说你不需要调用PyErr_Occurred()
来检查在函数调用中是否有错误发生,因为可以从函数的返回值中得知。
当函数f调用另一个函数g发现g失败了,f本身需要返回一个错误值(一般是NULL或者-1
)。它应该不调用PyErr_*()
函数之一 - g已调用它。 f的呼叫者还应向其 t>呼叫者返回错误指示,再次而不呼叫PyErr_*()
一旦错误到达了Python解释器的主循环,这会中止当前Python代码的执行,然后尝试寻找由Python程序员指定的异常处理器。
(模块可以通过调用PyErr_*()
函数来给出更详细的错误消息,这在某些情况下是可行的。尽管如此,作为一个通用的规则,这是不必要的,它有可能导致错误原因信息的丢失:大多数操作失败的原因千奇百怪。)
要忽略函数调用失败所设置的异常,可以通过调用PyErr_Clear()
来清除异常。唯一需要调用PyErr_Clear()
的情况是不希望向解释器传递错误,而是想完全由C代码自己来处理异常(可能是换种尝试或者假装什么事都没有发生)。
每一个失败的realloc()
调用必须转成一个异常——malloc()
(或者malloc()
)的直接调用者必须调用PyErr_NoMemory()
并且返回一个错误值。所有的对象创建函数(例如PyLong_FromLong()
)已经做到了,所以这个注释只与那些直接调用malloc()
的用户相关。
请注意PyArg_ParseTuple()
系列函数的例外,这些函数返回整数值来表示状态,一般正整数或者零表示成功,-1
表示失败,和Unix系统调用类似。
最后当返回错误值的时候要小心垃圾回收(对已经创建的对象调用Py_XDECREF()
或者Py_DECREF()
)!
抛出何种异常的选择权在于你。你可以直接使用预先声明的C对象,它们对应于内置的Python异常,如PyExc_ZeroDivisionError
。当然,你应该明智地选择异常——不要使用PyExc_IOError
来表示文件不能打开(这种情况应该使用PyExc_TypeError
)。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_INCREF(SpamError);
PyModule_AddObject(m, "error", SpamError);
return m;
}
注意异常对象的Python名字是spam.error
。PyErr_NewException()
创建一个类,它的基类是Exception
(除非传递了另一个类而不是NULL)(Exception在Built-in Exceptions中有描述)。
还要注意,SpamError
变量保留对新创建的异常类的引用;这是故意的!因为可以通过外部代码从模块中移除该异常,拥有对该类的引用是必要的,可以保证它不会被丢弃,从而SpamError
不会是个空指针。如果它成了空指针,抛出该异常的C代码将会导致内存转储或者其它意外的副作用。
我们将在这个例子的后面讨论函数返回类型PyMODINIT_FUNC
。
可以在扩展模块中通过调用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()
函数必须以Python对象的方式返回值sts
。这是使用函数PyLong_FromLong()
完成的。
return PyLong_FromLong(sts);
这里,它返回一个整数对象。(在Python中,整数也是在堆上的对象!)
如果C函数不需要返回值(函数返回void
),对应的Python函数必须返回None
。你可以这样做(也可以调用Py_RETURN_NONE
宏):
Py_INCREF(Py_None);
return Py_None;
Py_None
是Python对象None
在C中的名字。它是一个真正的Python对象,而不是NULL指针(空指针在大多数上下文表示“错误”)。
1.4. The Module’s Method Table and Initialization 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_VARARGS
或METH_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函数必须将模块对象返回给其调用者,以便将其插入到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 */
PyImport_AppendInittab("spam", PyInit_spam);
/* Pass argv[0] to the Python interpreter */
Py_SetProgramName(program);
/* Initialize the Python interpreter. Required. */
Py_Initialize();
/* Optionally import the module; alternatively,
import can be deferred until the embedded script
imports it. */
PyImport_ImportModule("spam");
...
PyMem_RawFree(program);
return 0;
}
注意
从sys.modules
中移除条目,或者向一个进程中的多个解释器导入编译过的模块(或者调用exec()
后没有调用fork()
),可能会对某些扩展模块带来问题。扩展模块作者在初始化内部数据结构时应谨慎。
在Python源码包中Modules/xxmodule.c
里有个更实际的例子。该文件可以当成模板,或者只是当成例子来读。
注意
与我们的spam
示例不同,xxmodule
使用多阶段初始化(Python 3.5中的新功能),其中PyModuleDef结构从PyInit_spam
,并且创建模块留给导入机制。有关多相初始化的详细信息,请参见 PEP 489。
1.5. Compilation and Linkage¶
在你能够使用新扩展之前,你得先将其编译和链接到Python系统。如果使用动态加载,细节可能取决于系统使用的动态加载的样式;请参阅有关构建扩展模块的章节(Building C and C++ Extensions)以及仅适用于在Windows上构建的其他信息(第Building C and C++ Extensions on Windows)更多信息。
如果你无法使用动态装载,或者你希望你的模块永久的成为解释器的一部分,你需要修改配置设置并重新构建解释器。幸运的是,这在Unix上非常简单:在解压的源码包中的Modules/Setup.local
目录下放置你的文件(例子中的spammodule.c
),在Modules/
中添加一行来描述你的文件:
spam spammodule.o
然后在顶层目录下运行make来重新构建解释器。你也可以在Makefile
子目录下运行make,但是你得先运行“make Makefile”以重新构建Modules/
。(每次修改了Setup
文件都要这样做。)
如果模块需要链接额外的库,它们也可以出现在配置文件中,例如:
spam spammodule.o -lX11
1.6. C调用python函数¶
到目前为止,我们已经重点讨论了如何从Python调用C的函数。反过来也是有用的:从C调用Python函数。对于支持所谓的“回调”函数的库,情况尤其如此。如果一个C接口使用回调,等效的Python通常需要为Python程序员提供一个回调机制;该实现将需要从C回调中调用Python回调函数。当然,也有很多其它的用途。
幸运的是,Python解释器可以简单的调用递归,并且有一个标准的调用接口去调用Python函数。 (I won’t dwell on how to call the Python parser with a particular string as input — if you’re interested, have a look at the implementation of the -c
command line option in Modules/main.c
from the Python source code.)
调用Python函数很容易。 Python程序一定会以某种方式传递给你Python的函数对象。 为此,你应该为其提供一个函数(或其它某种接口)。当调用这个函数时,保存一个指向Python函数对象的指针(注意Py_INCREF()
it!)在一个全局变量 - 或任何你认为合适。比如:以下的函数可能成为模块定义的一部分
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
标志向解释器注册;这在The Module’s Method Table and Initialization Function中有描述。PyArg_ParseTuple()
函数及其参数在Extracting Parameters in Extension Functions一节中有说明。
宏Py_XINCREF()
和Py_XDECREF()
增加/减少对象的引用计数,并且在存在NULL指针的情况下是安全的请注意,在此上下文中,temp不会是NULL)。有关详细信息,请参见Reference Counts部分。
以后当需要调用这个回调函数时,调用 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()
相对于其参数是“引用计数中性”。在示例中,创建了一个新的元组作为参数列表,它在PyObject_CallObject()
调用之后直接执行Py_DECREF()
PyObject_CallObject()
的返回值是“new”:它是一个全新的对象,或者它是一个引用计数已增加的现有对象。所以,除非你想保存在一个全局变量,你应该以某种方式Py_DECREF()
的结果,甚至(特别是!如果你对它的价值不感兴趣。
然而,在执行此操作之前,重要的是检查返回值不是NULL。如果是,Python函数通过提出异常终止。如果调用PyObject_CallObject()
的C代码是从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. Extracting Parameters in Extension Functions¶
函数 PyArg_ParseTuple()
定义如下:
int PyArg_ParseTuple(PyObject *arg, const char *format, ...);
arg参数必须是包含从Python传递到C函数的参数列表的元组对象。格式参数必须是格式字符串,其语法在Python / C API参考手册的Parsing arguments and building values中进行了说明。其余参数必须是其类型由格式字符串确定的变量的地址。
注意,虽然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()
函数的参数相同。kwdict参数是从Python运行时作为第三个参数接收的关键字的字典。kwlist参数是一个NULL - 标识参数的字符串列表;这些名称与从格式的类型信息从左到右相匹配。成功时,PyArg_ParseTupleAndKeywords()
返回true,否则返回false并引发适当的异常。
注意
使用关键字参数时,无法解析嵌套元组!传递的关键字参数(不在kwlist中)将导致TypeError
提高。
下面是一个使用关键字的示例模块,基于Geoff Philbrick(philbrick @ hks 。 com)的示例:
#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_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)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 ++中,使用运算符new
和delete
具有基本相同的含义,并且我们将以下讨论限制为C语言。
分配有malloc()
的每个内存块最终只需调用一次free()
就可以返回到可用内存池中。重要的是在正确的时间调用free()
。如果块的地址被遗忘,但不调用free()
,则它占用的内存不能重复使用,直到程序终止。这称为内存泄漏。另一方面,如果程序为块调用free()
,然后继续使用该块,则通过另一个malloc()
这称为使用释放内存。它与引用未初始化数据(核心转储,错误结果,神秘崩溃)具有相同的不良后果。
内存泄漏的常见原因是通过代码的不寻常路径。对于实例,函数可以分配一块存储器,进行一些计算,然后再次释放该块。现在,对函数的需求的改变可以向检测错误条件的计算添加测试,并且可以从函数过早地返回。当采取这种提前退出时,很容易忘记释放分配的内存块,特别是当它以后添加到代码时。这种泄漏一旦被引入,通常会长时间未被检测到:错误退出仅在所有调用的一小部分中发生,并且大多数现代机器具有大量虚拟内存,因此泄漏仅在长时间运行的过程中变得明显经常使用泄漏功能。因此,通过具有使这种错误最小化的编码约定或策略来防止泄漏的发生是很重要的。
由于Python大量使用malloc()
和free()
,它需要一个策略来避免内存泄漏以及释放内存的使用。所选的方法称为引用计数。原理很简单:每个对象包含一个计数器,当对对象的引用存储在某处时,该计数器递增,并且当对其的引用被删除时,该计数器递减。当计数器达到零时,对象的最后一个引用被删除,并且对象被释放。
另一种策略称为自动垃圾容器。(有时,引用计数也被称为垃圾容器策略,因此我使用“自动”来区分两者)。自动垃圾容器的最大优点是用户不需要显式地调用free()
。(另一个声称的优势是速度或内存使用的改进 - 但这不是硬的事实。)缺点是对于C,没有真正的便携式自动垃圾回收器,而引用计数可以可移植地实现(只要函数malloc()
和free()
也许有一天,一个充分便携的自动垃圾回收器将可用于C.直到那时,我们将不得不生活的引用计数。
虽然Python使用传统的引用计数实现,它还提供了一个周期检测器,用于检测参考周期。这允许应用程序不必担心创建直接或间接循环引用;这些都是垃圾容器实现的弱点只使用引用计数。引用循环由包含(可能是间接的)对自身的引用的对象组成,使得循环中的每个对象具有非零的引用计数。典型的引用计数实现不能回收属于参考循环中的任何对象的存储器,或者从循环中的对象引用,即使没有对循环本身的进一步引用。
循环检测器能够检测垃圾循环并可以回收它们。gc
模块公开了运行检测器的方法(collect()
函数),以及配置界面和在运行时禁用检测器的能力。周期检测器被认为是可选组件;虽然默认情况下包括它,但可以在构建时使用Unix平台上的configure脚本的--without-cycle-gc
选项(包括Mac OS X )。如果循环检测器以这种方式禁用,则gc
模块将不可用。
1.10.1. Python中的引用计数¶
有两个宏,Py_INCREF(x)
和Py_DECREF(x)
,它们处理引用计数的递增和递减。Py_DECREF()
会在引用计数为0时释放对象。为了灵活性,它不直接调用free()
- 而是通过对象的类型对象中的函数指针进行调用。为了这个目的(和其他),每个对象还包含一个指向它的类型对象的指针。
现在的大问题是:何时使用Py_INCREF(x)
和Py_DECREF(x)
?让我们先介绍一些术语。没有人拥有一个对象;但是,您可以拥有对象的引用。对象的引用计数现在定义为对它的拥有引用的数量。当不再需要引用时,引用的所有者负责调用Py_DECREF()
。参考的所有权可以转移。有三种方法来处理一个拥有的引用:传递它,存储它,或调用Py_DECREF()
。忘记处理拥有的引用会造成内存泄漏。
也可以借用 [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]
的引用,然后用值0
替换list[1]
,最后打印借用的引用。看起来无害,对吧?但它不是!
让我们按照控制流程进入PyList_SetItem()
。该列表拥有对其所有项目的引用,因此当替换项目1时,它必须处理原始项目1。现在让我们假设原始项1是用户定义类的实例,让我们进一步假设该类定义了一个__del__()
方法。如果这个类实例的引用计数为1,则处理它将调用其__del__()
方法。
由于它是用Python编写的,所以__del__()
方法可以执行任意的Python代码。它可能会做一些事情使bug()
中item
的引用无效吗?你打赌!假设传递到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
重新获取它。显然,以下功能具有与前一个相同的问题:
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 Pointers¶
通常,函数接受一个对象作为一个参数是不希望你传给它们一个空指针(NULL)的,如果你这样做的话,会引发触核或者导致触核。函数返回一个对象的引用,一般只有在指示一个异常发生时才会返回空指针(NULL )。不测试NULL参数的原因是函数通常将它们接收的对象传递给其他函数 - 如果每个函数都要测试NULL,那么会有很多的冗余测试,代码运行更慢。
It is better to test for NULL only at the “source:” when a pointer that may be NULL is received, for example, from malloc()
or from a function that may raise an exception.
The macros Py_INCREF()
and Py_DECREF()
do not check for NULL pointers — however, their variants Py_XINCREF()
and Py_XDECREF()
do.
用于检查特定对象类型的宏(Pytype_Check()
)不检查NULL指针 - 再次,有很多代码在一行中调用其中的几个以测试对象对各种不同的预期类型,这将产生冗余测试。没有使用NULL检查的变体。
C函数调用机制保证传递给C函数(在示例中args
)的参数列表永远不是NULL - 实际上它保证它总是一个元组。[4]
将NULL指针“转义”给Python用户是一个严重的错误。
1.11. Writing Extensions in C++¶
可以在C ++中编写扩展模块。有一些限制。如果主程序(Python解释器)由C编译器编译和链接,则不能使用带有构造函数的全局或静态对象。如果主程序由C ++编译器链接,这不是一个问题。需要使用extern “C”
来声明将由Python解释器调用的函数(特别是模块初始化函数)。不必在extern “C” {...}
中包含Python头文件 - 如果定义符号__cplusplus
(所有最近的C ++编译器定义此符号),此形式已经存在。
1.12. Providing a C API for an Extension Module¶
许多扩展模块只提供要从Python使用的新函数和类型,但有时扩展模块中的代码对其他扩展模块很有用。例如,扩展模块可以实现类型“容器”,其工作方式类似于没有顺序的列表。就像标准的Python列表类型有一个C API允许扩展模块创建和操作列表,这个新的容器类型应该有一个C函数直接操作从其他扩展模块。
看起来这很容易:只写函数(当然,没有声明static
),提供一个合适的头文件,并记录C API。事实上,如果所有扩展模块总是与Python解释器静态链接,这将工作。但是,当模块用作共享库时,一个模块中定义的符号可能对另一个模块不可见。可见性的详细信息取决于操作系统;一些系统为Python解释器和所有扩展模块(例如Windows)使用一个全局命名空间,而其他系统在模块链接时需要显式导入的符号列表(AIX是一个示例),或提供不同策略的选择大多数Unices)。即使符号是全局可见的,那么其希望调用的函数的模块可能尚未加载!
因此,可移植性不需要对符号可见性做任何假设。这意味着扩展模块中的所有符号应该声明为static
,除了模块的初始化函数,以避免与其他扩展模块发生名称冲突(如The Module’s Method Table and Initialization Function)。这意味着应该的符号必须从其他扩展模块访问,必须以不同的方式导出。
Python提供了一个特殊的机制,将C级信息(指针)从一个扩展模块传递到另一个扩展模块:Capsules。Capsule是一种存储指针(void *
)的Python数据类型。胶囊只能通过C API创建和访问,但它们可以像任何其他Python对象一样传递。特别地,它们可以被分配给扩展模块的命名空间中的名称。其他扩展模块然后可以导入此模块,检索此名称的值,然后从Capsule检索指针。
有很多方法可以使用Capsule来导出扩展模块的C API。每个函数可以获得自己的Capsule,或者所有C API指针可以存储在一个数组中,该数组的地址发布在Capsule中。并且存储和检索指针的各种任务可以在提供代码的模块和客户端模块之间以不同的方式分布。
无论选择哪种方法,正确命名您的胶囊很重要。函数PyCapsule_New()
采用名称参数(const char *
);您可以传入NULL名称,但我们强烈建议您指定名称。正确命名的胶囊提供一定程度的运行时类型安全;没有可行的方法来告诉一个未命名的胶囊从另一个。
特别是,用于公开C API的Capsule应该按照这个约定命名:
modulename.attributename
方便函数PyCapsule_Import()
使得容易加载通过Capsule提供的C API,但前提是Capsule的名称匹配这个约定。这种行为为C API用户提供了高度的确定性,他们加载的Capsule包含正确的C API。
以下示例演示了一种将导致模块的编写器的大部分负担的方法,这对于常用的库模块是合适的。它存储所有C API指针(在示例中只有一个!)在void
指针的数组中,它成为Capsule的值。与模块相对应的头文件提供了一个宏,该宏负责导入模块并检索其C API指针;客户端模块只有在访问C API之前调用此宏。
导出模块是对A Simple Example部分中spam
模块的修改。函数spam.system()
不直接调用C库函数system()
,而是函数PySpam_System()
课程在现实中做一些更复杂的事情(如为每个命令添加“垃圾邮件”)。此函数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 (c_api_object != NULL)
PyModule_AddObject(m, "_C_API", c_api_object);
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提供额外的功能,这是特别有用的内存分配和释放存储在Capsule中的指针。详细信息在Python / C API参考手册的Capsules部分和Capsules(文件Include/pycapsule.h
和Objects/pycapsule.c
)。
脚注
[1] | 该功能的接口已经存在于标准模块os 中 - 它被选为一个简单而直接的例子。 |
[2] | “借用”参考的比喻不完全正确:所有者仍然有参考文献的副本。 |
[3] | 检查引用计数至少为1 不起作用 - 引用计数本身可以在释放的内存中,因此可以重用于另一个对象! |
[4] | 当使用“旧”样式调用约定时,这些保证不成立 - 这仍然存在于许多现有代码中。 |