C拓展Python

转载: http://www.sudu.cn/info/html/edu/20070101/283296.html
使用C/C++扩展Python

如果你会用C,实现Python嵌入模块很简单。利用扩展模块可做很多Python不方便做的事情,他们可以直接调用C库和系统调用。
为了支持扩展,Python API定义了一系列函数、宏和变量,提供了对Python运行时系统的访问支持。Python的C API由C源码组成,并包含  “Python.h” 头文件。
编写扩展模块与你的系统相关,下面会详解。
目录

1   一个简单的例子

2   关于错误和异常

3   回到例子

4   模块方法表和初始化函数

5   编译和连接

6   在C中调用Python函数

7   解析传给扩展模块函数的参数

8   解析传给扩展模块函数的关键字参数

Array   构造任意值

10   引用计数

10.1   Python中的引用计数

10.2   拥有规则

10.3   危险的薄冰

10.4   NULL指针


11   使用C++编写扩展

12   提供给其他模块以C API

1   一个简单的例子
下面的例子创建一个叫做 “spam” 的扩展模块,调用C库函数 system()  。这个函数输入一个NULL结尾的字符串并返回整数,可供Python调用方式如下:
>>> import spam
>>> status=spam.system("ls -l")
一个C扩展模块的文件名可以直接是 模块名.c 或者是 模块名module.c  。第一行应该导入头文件:
#include 
这会导入Python API。
Warning
因为Python含有一些预处理定义,所以你必须在所有非标准头文件导入之前导入Python.h 。
Python.h中所有用户可见的符号都有 Py 或 PY 的前缀,除非定义在标准头文件中。为了方便
“Python.h”
也包含了一些常用的标准头文件,包括,,,。如果你的系统没有后面的头文件,则会直接定义函数 malloc() 、 free() 和 realloc() 。
下面添加C代码到扩展模块,当调用 “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 Py_BuildValue("i",sts);
}
调用方的Python只有一个命令参数字符串传递到C函数。C函数总是有两个参数,按照惯例分别叫做 self 和  args 。
self 参数仅用于用C实现内置方法而不是函数。本例中, self  总是为NULL,因为我们定义的是个函数,不是方法。这一切都是相同的,所以解释器也就不需要刻意区分两种不同的C函数。
args
参数是一个指向Python的tuple对象的指针,包含参数。每个tuple子项对应一个调用参数。这些参数也全都是Python对象,所以需要先转换
成C值。函数 PyArg_ParseTuple() 检查参数类型并转换成C值。它使用模板字符串检测需要的参数类型。
PyArg_ParseTuple()  正常返回非零,并已经按照提供的地址存入了各个变量值。如果出错(零)则应该让函数返回NULL以通知解释器出错。
2   关于错误和异常
一个常见惯例是,函数发生错误时,应该设置一个异常环境并返回错误值(NULL)。异常存储在解释器静态全局变量中,如果为NULL,则没有发生异
常。异常的第一个参数也需要保存在静态全局变量中,也就是raise的第二个参数。第三个变量包含栈回溯信息。这三个变量等同于Python变量
sys.exc_type 、 sys.exc_value 、 sys.exc_traceback 。这对找到错误是很必要的。
Python API中定义了一些函数来设置这些变量。
最常用的就是 PyErr_SetString() 。参数是异常对象和C字符串。异常对象一般由像  PyExc_ZeroDivisionError 这样的对象来预定义。C字符串指明异常原因,并最终存储在异常的第一个参数里面。
另一个有用的函数是 PyErr_SetFromErrno() ,仅接受一个异常对象,异常描述包含在全局变量 errno
中。最通用的函数还是 PyErr_SetObject() ,包含两个参数,分别为异常对象和异常描述。你不需要使用 Py_INCREF()
来增加传递到其他函数的参数对象的引用计数。
你可以通过 PyErr_Occurred()  获知当前异常,返回当前异常对象,如果确实没有则为NULL。一般来说,你在调用函数时不需要调用 PyErr_Occurred()  检查是否发生了异常,你可以直接检查返回值。
如果调用更下层函数时出错了,那么本函数返回NULL表示错误,并且整个调用栈中只要有一处调用 PyErr_*()  函数设置异常就可以。一般来说,首先发现错误的函数应该设置异常。一旦这个错误到达了Python解释器的主循环,则会中断当前执行代码并追究异常。
有一种情况下,模块可能依靠其他 PyErr_*()  函数给出更加详细的错误信息,并且是正确的。但是按照一般规则,这并不重要,很多操作都会因为种种原因而挂掉。
想要忽略这些函数设置的异常,异常情况必须明确的使用 PyErr_Clear()  来清除。只有在C代码想要自己处理异常而不是传给解释器时才这么做。
每次失败的 malloc()  调用必须抛出一个异常,直接调用 malloc() 或 realloc() 的地方要调用  PyErr_NoMemory() 并返回错误。所有创建对象的函数都已经实现了这个异常的抛出,所以这是每个分配内存都要做的。
还要注意的是 PyArg_ParseTuple()  系列函数的异常,返回一个整数状态码是有效的,0是成功,-1是失败,有如Unix系统调用。
最后,小心垃圾情理,也就是 Py_XDECREF() 和 Py_DECREF()  的调用,会返回的异常。
选择抛出哪个异常完全是你的个人爱好了。有一系列的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 NULL;
    SpamError=PyErr_NewException("spam.error",NULL,NULL);
    Py_INCREF(SpamError);
    PyModule_AddObject(m,"error",SpamError);
}
注意实际的Python异常名字是 spam.error 。 PyErr_NewException()  函数使用Exception为基类创建一个类(除非是使用另外一个类替代NULL)。
同样注意的是创建类保存了SpamError的一个引用,这是有意的。为了防止被垃圾回收掉,否则SpamError随时会成为野指针。
一会讨论 PyMODINIT_FUNC 作为函数返回类型的用法。
3   回到例子
回到前面的例子,你应该明白下面的代码:
if (!PyArg_ParseTuple(args,"s",&command))
    return NULL;
就是为了报告解释器一个异常。如果执行正常则变量会拷贝到本地,后面的变量都应该以指针的方式提供,以方便设置变量。本例中的command会被声明为  “const char* command” 。
下一个语句使用UNIX系统函数system(),传递给他的参数是刚才从 PyArg_ParseTuple() 取出的:
sts=system(command);
我们的 spam.system() 函数必须返回一个PY对象,这可以通过  Py_BuildValue() 来完成,其形式与 PyArg_ParseTuple()  很像,获取格式字符串和C值,并返回新的Python对象:
return Py_BuildValue("i",sts);
在这种情况下,会返回一个整数对象,这个对象会在Python堆里面管理。
如果你的C函数没有有用的返回值,则必须返回None。你可以用 Py_RETUN_NONE 宏来完成:
Py_INCREF(Py_None);
return Py_None;
Py_None 是一个C名字指定Python对象None。这是一个真正的PY对象,而不是NULL指针。
4   模块方法表和初始化函数
把函数声明为可以被Python调用,需要先定义一个方法表:
static PyMethodDef SpamMethods[]= {
    ...
    {"system",spam_system,METH_VARARGS,
    "Execute a shell command."},
    ...
    {NULL,NULL,0,NULL}    /*必须的结束符*/
};
注意第三个参数 METH_VARARGS ,这个标志指定会使用C的调用惯例。可选值有  METH_VARARGS 、 METH_VARARGS | METH_KEYWORDS 。值0代表使用  PyArg_ParseTuple() 的陈旧变量。
如果单独使用 METH_VARARGS ,函数会等待Python传来tuple格式的参数,并最终使用  PyArg_ParseTuple() 进行解析。
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程序首次导入这个模块时就会调用initspam()函数。他调用 Py_InitModule()
来创建一个模块对象,同时这个模块对象会插入到 sys.modules 字典中的 “spam” 键下面。然后是插入方法表中的内置函数到
“spam” 键下面。 Py_InitModule()
返回一个指针指向刚创建的模块对象。他是有可能发生严重错误的,也有可能在无法正确初始化时返回NULL。
当嵌入Python时, initspam() 函数不会自动被调用,除非在入口处的  _PyImport_Inittab 表。最简单的初始化方法是在 Py_Initialize() 之后静态调用  initspam() 函数:
int
main(int argc, char* argv[]) {
    Py_SetProgramName(argv[0]);
    Py_Initialize();
    initspam();
    //...
}
在Python发行版的 Demo/embed/demo.c 中有可以参考的源码。
Note
从 sys.modules
中移除模块入口,或者在多解释器环境中导入编译模块,会导致一些扩展模块出错。扩展模块作者应该特别注意初始化内部数据结构。同时要注意
reload() 函数可能会被用在扩展模块身上,并调用模块初始化函数,但是对动态状如对象(动态链接库),却不会重新载入。
更多关于模块的现实的例子包含在Python源码包的Modules/xxmodule.c中。这些文件可以用作你的代码模板,或者学习。脚本  modulator.py  包含在源码发行版或Windows安装中,提供了一个简单的GUI,用来声明需要实现的函数和对象,并且可以生成供填入的模板。脚本在 Tools/modulator/  目录。查看README以了解用法。
5   编译和连接
如果使用动态载入,细节依赖于系统,查看关于构建扩展模块部分,和关于在Windows下构建扩展的细节。
如果你无法使用动态载入,或者希望模块成为Python的永久组成部分,就必须改变配置并重新构建解释器。幸运的是,这对UNIX来说很简单,只要
把你的代码(例如spammodule.c)放在 Modules/ Python源码目录下,然后增加一行到文件
Modules/Setup.local 来描述你的文件即可:
spam spammodule.o
然后重新构建解释器,使用make。你也可以在 Modules/ 子目录使用make,但是你接下来首先要重建Makefile文件,使用 make Makefile 命令。这对你改变 Setup 文件来说很重要。
如果你的模块需要其他扩展模块连接,则需要在配置文件后面加入,如:
spam spammodule.o -lX11
6   在C中调用Python函数
迄今为止,我们一直把注意力集中于让Python调用C函数,其实反过来也很有用,就是用C调用Python函数。这在回调函数中尤其有用。如果一个C接口使用回调,那么就要实现这个回调机制。
幸运的是,Python解释器是比较方便回调的,并给标准Python函数提供了标准接口。这里就不再详述解析Python代码作为输入的方式,如果有兴趣可以参考  Python/pythonmain.c 中的  -c 命令代码。
调用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);
        Py_XINCREF(my_callback);
        my_callback=temp;
        Py_INCREF(Py_None);
        result=Py_None;
    }
    return result;
}
这个函数必须使用 METH_VARARGS 标志注册到解释器。宏 Py_XINCREF() 和  Py_XDECREF() 增加和减少对象的引用计数。
然后,就要调用函数了,使用 PyEval_CallObject()
。这个函数有两个参数,都是指向Python对象:Python函数和参数列表。参数列表必须总是tuple对象,如果没有参数则要传递空的tuple。
使用 Py_BuildValue() 时,在圆括号中的参数会构造成tuple,无论有没有参数,如:
int arg;
PyObject* arglist;
PyObject* result;
//...
arg=123;
//...
arglist=Py_BuildValue("(i)",arg);
result=PyEval_CallObject(my_callback,arglist);
Py_DECREF(arglist);
PyEval_CallObject() 返回一个Python对象指针表示返回值。  PyEval_CallObject() 是 引用计数无关  的,有如例子中,参数列表对象使用完成后就立即减少引用计数了。`PyEval_CallObject()` 返回一个Python对象指针表示返回值。  PyEval_CallObject() 是 引用计数无关  的,有如例子中,参数列表对象使用完成后就立即减少引用计数了。
PyEval_CallObject()
的返回值总是新的,新建对象或者是对已有对象增加引用计数。所以你必须获取这个对象指针,在使用后减少其引用计数,即便是对返回值没有兴趣也要这么做。但
是在减少这个引用计数之前,你必须先检查返回的指针是否为NULL。如果是NULL,则表示出现了异常并中止了。如果没有处理则会向上传递并最终显示调用
栈,当然,你最好还是处理好异常。如果你对异常没有兴趣,可以用 PyErr_Clear() 清除异常,例如:
if (result==NULL)
    return NULL;  /*向上传递异常*/
//使用result
Py_DECREF(result);
依赖于具体的回调函数,你还要提供一个参数列表到 PyEval_CallObject()
。在某些情况下参数列表是由Python程序提供的,通过接口再传到回调函数。这样就可以不改变形式直接传递。另外一些时候你要构造一个新的tuple来
传递参数。最简单的方法就是 Py_BuildValue() 函数构造tuple。例如,你要传递一个事件对象时可以用:
PyObject* arglist;
//...
arglist=Py_BuildValue("(l)",eventcode);
result=PyEval_CallObject(my_callback,arglist);
Py_DECREF(arglist);
if (result==NULL)
    return NULL;  /*一个错误*/
/*使用返回值*/
Py_DECREF(result);
注意 Py_DECREF(arglist) 所在处会立即调用,在错误检查之前。当然还要注意一些常规的错误,比如  Py_BuildValue() 可能会遭遇内存不足等等。
7   解析传给扩展模块函数的参数
函数 PyArg_ParseTuple() 声明如下:
int PyArg_ParseTuple(PyObject* arg, char* format, ...);
参数 arg 必须是一个tuple对象,包含传递过来的参数, format  参数必须是格式化字符串,语法解释见 “Python C/API” 的5.5节。剩余参数是各个变量的地址,类型要与格式化字符串对应。
注意 PyArg_ParseTuple()  会检测他需要的Python参数类型,却无法检测传递给他的C变量地址,如果这里出错了,可能会在内存中随机写入东西,小心。
任何Python对象的引用,在调用者这里都是 借用的引用 ,而不增加引用计数。
一些例子:
int ok;
int i,j;
long k,l;
const char* s;
int size;
ok=PyArg_ParseTuple(args,"");
/* python call: f() */
ok=PyArg_ParseTuple(args,"s",&s);
/* python call: f(’whoops!’) */
ok=PyArg_ParseTuple(args,"lls",&k,&l,&s);
/* python call: f(1,2,’three’) */
ok=PyArg_ParseTuple(args,"(ii)s#",&i,&j,&s,&size);
/* 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);
    /* python call:
        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);
    /* python call: f(((0,0),(400,300)),(10,10)) */
}
{
    Py_complex c;
    ok=PyArg_ParseTuple(args,"D:myfunction",&c);
    /* python call: myfunction(1+2j) */
}
8   解析传给扩展模块函数的关键字参数
函数 PyArg_ParseTupleAndKeywords() 声明如下:
int PyArg_ParseTupleAndKeywords(PyObject* arg, PyObject* kwdict, char* format, char* kwlist[],...);
参数arg和format定义同 PyArg_ParseTuple() 。参数 kwdict
是关键字字典,用于接受运行时传来的关键字参数。参数 kwlist
是一个NULL结尾的字符串,定义了可以接受的参数名,并从左到右与format中各个变量对应。如果执行成功
PyArg_ParseTupleAndKeywords() 会返回true,否则返回false并抛出异常。
Note
嵌套的tuple在使用关键字参数时无法生效,不在kwlist中的关键字参数会导致  TypeError 异常。
如下是使用关键字参数的例子模块,作者是 Geoff Philbrick (
phibrick@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_INCREF(Py_None);
    return Py_None;
}
static PyMethodDef keywdary_methods[]= {
    /*注意PyCFunction,这对需要关键字参数的函数很必要*/
    {"parrot",(PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,"Print a lovely skit to standard output."},
    {NULL,NULL,0,NULL}
};
void
initkeywdarg(void) {
    Py_InitModule("keywdarg",keywdarg_methods);
}
Array   构造任意值
这个函数声明与 PyArg_ParseTuple() 很相似,如下:
PyObject* Py_BuildValue(char* format, ...);
接受一个格式字符串,与 PyArg_ParseTuple()  相同,但是参数必须是原变量的地址指针。最终返回一个Python对象适合于返回给Python代码。
一个与 PyArg_ParseTuple() 的不同是,后面可能需要的要求返回一个tuple,比如用于传递给其他Python函数以参数。
Py_BuildValue()
并不总是生成tuple,在多于1个参数时会生成tuple,而如果没有参数则返回None,一个参数则直接返回该参数的对象。如果要求强制生成一个长度
为空的tuple,或包含一个元素的tuple,需要在格式字符串中加上括号。
例如:
代码
返回值
Py_BuildValue(”")
None
Py_BuildValue(”i”,123)
123
Py_BuildValue(”iii”,123,456,78Array)
(123,456,78Array)
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}”,’a’,1,’b’,2)
{’a’:1,’b’:2}
Py_BuildValue(”((ii)(ii))(ii)”,1,2,3,4,5,6)
(((1,2),(3,4)),(5,6))
10   引用计数
在C/C++语言中,程序员负责动态分配和回收堆(heap)当中的内存。这意味着,我们在C中编程时必须面对这个问题。
每个由 malloc() 分配的内存块,最终都要由 free() 扔到可用内存池里面去。而调用 free()
的时机非常重要,如果一个内存块忘了 free() 则是内存泄漏,程序结束前将无法重新使用。而如果对同一内存块 free()
了以后,另外一个指针再次访问,则叫做野指针。这同样会导致严重的问题。
内存泄露往往发生在一些并不常见的程序流程上面,比如一个函数申请了资源以后,却提前返回了,返回之前没有做清理工作。人们经常忘记释放资源,尤其
对于后加新加的代码,而且会长时间都无法发现。这些函数往往并不经常调用,而且现在大多数机器都有庞大的虚拟内存,所以内存泄漏往往在长时间运行的进程,
或经常被调用的函数中才容易发现。所以最好有个好习惯加上代码约定来尽量避免内存泄露。
Python往往包含大量的内存分配和释放,同样需要避免内存泄漏和野指针。他选择的方法就是 引用计数  。其原理比较简单:每个对象都包含一个计数器,计数器的增减与引用的增减直接相关,当引用计数为0时,表示对象已经没有存在的意义了,就可以删除了。
一个叫法是 自动垃圾回收 ,引用计数是一种垃圾回收方法,用户必须要手动调用 free()
函数。优点是可以提高内存使用率,缺点是C语言至今也没有一个可移植的自动垃圾回收器。引用计数却可以很好的移植,有如C当中的 malloc() 和
free() 一样。也许某一天会出现C语言饿自动垃圾回收器,不过在此之前我们还得用引用计数。
Python使用传统的引用计数实现,不过他包含一个循环引用探测器。这允许应用不需要担心的直接或间接的创建循环引用,而这实际上是引用计数实现
的自动垃圾回收的致命缺点。循环引用指对象经过几层引用后回到自己,导致了其引用计数总是不为0。传统的引用计数实现无法解决循环引用的问题,尽管已经没
有其他外部引用了。
循环引用探测器可以检测出垃圾回收中的循环并释放其中的对象。只要Python对象有 __del__()  方法,Python就可以通过 gc module 模块来自动暴露出循环引用。gc模块还提供 collect()  函数来运行循环引用探测器,可以在配置文件或运行时禁用循环应用探测器。
循环引用探测器作为一个备选选项,默认是打开的,可以在构建时使用 ?without-cycle-gc 选项加到  configure 上来配置,或者移除 pyconfig.h 文件中的 WITH_CYCLE_GC  宏定义。在循环引用探测器禁用后,gc模块将不可用。
10.1   Python中的引用计数
有两个宏 Py_INCREF(x) 和 Py_DECREF(x) 用于增减引用计数。  Py_DECREF() 同时会在引用计数为0时释放对象资源。为了灵活性,他并不是直接调用 free()  而是调用对象所在类型的析构函数。
一个大问题是何时调用 Py_INCREF(x) 和 Py_DECREF(x)  。首先介绍一些术语。没有任何人都不会 拥有  一个对象,只能拥有其引用。对一个对象的引用计数定义了引用数量。拥有的引用,在不再需要时负责调用 Py_DECREF()  来减少引用计数。传递引用计数有三种方式:传递、存储和调用 Py_DECREF() 。忘记减少拥有的引用计数会导致内存泄漏。
同样重要的一个概念是 借用 一个对象,借用的对象不能调用 Py_DECREF()  来减少引用计数。借用者在不需要借用时,不保留其引用就可以了。应该避免拥有者释放对象之后仍然访问对象,也就是野指针。
借用的优点是你无需管理引用计数,缺点是可能被野指针搞的头晕。借用导致的野指针问题常发生在看起来无比正确,但是事实上已经被释放的对象。
借用的引用也可以用 Py_INCREF()  来改造成拥有的引用。这对引用的对象本身没什么影响,但是拥有引用的程序有责任在适当的时候释放这个拥有。
10.2   拥有规则
一个对象的引用进出一个函数时,其引用计数也应该同时改变。
大多数函数会返回一个对对象拥有的引用。而且几乎所有的函数其实都会创建一个对象,例如 PyInt_FromLong() 和
Py_BuildValue() ,传递一个拥有的引用给接受者。即便不是刚创建的,你也需要接受一个新的拥有引用。一般来说,
PyInt_FromLong() 会维护一个常用值缓存,并且返回缓存项的引用。
很多函数提取一些对象的子对象并传递拥有引用,例如 PyObject_GetAttrString() 。另外,小心一些函数,包括:
PyTuple_GetItem() 、 PyList_GetItem() 、 PyDict_GetItem() 和
PyDict_GetItemString() ,他们返回的都是借用的引用。
函数 PyImport_AddModule() 也是返回借用的引用,尽管他实际上创建了对象,只不过其拥有的引用实际存储在了  sys.modules 中。
当你传递一个对象的引用到另外一个函数时,一般来说,函数是借用你的引用,如果他确实需要存储,则会使用 Py_INCREF()
来变为拥有引用。这个规则有两种可能的异常: PyTuple_SetItem() 和 PyList_SetItem()
,这两个函数获取传递给他的拥有引用,即便是他们执行出错了。不过 PyDict_SetItem() 却不是接收拥有的引用。
当一个C函数被py调用时,使用对参数的借用。调用者拥有参数对象的拥有引用。所以,借用的引用的寿命是函数返回。只有当这类参数必须存储时,才会使用  Py_INCREF() 变为拥有的引用。
从C函数返回的对象引用必须是拥有的引用,这时的拥有者是调用者。
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] ,然后把 list[1]  替换为值0,最后打印借用的引用。看起来正确么,不是!
我们来跟踪一下 PyList_SetItem()
的控制流,列表拥有所有元素的引用,所以当项目1被替换时,他就释放了原始项目1。而原始项目1是一个用户定义类的实例,假设这个类定义包含
__del__() 方法。如果这个类的实例引用计数为1,处理过程会调用 __del__() 方法。
因为使用python编写,所以 __del__() 中可以用任何python代码来完成释放工作。替换元素的过程会执行 del list[0] ,即减掉了对象的最后一个引用,然后就可以释放内存了。
知道问题后,解决方案就出来了:临时增加引用计数。正确的版本如下:
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中多处包含这个问题,让guido花费大量时间研究 __del__()  为什么失败了。
第二种情况的问题出现在多线程中的借用引用。一般来说,python中的多线程之间并不能互相影响对方,因为存在一个GIL。不过,这可能使用宏
Py_BEGIN_ALLOW_THREADS 来临时释放锁,最后通过宏 Py_END_ALLOW_THREADS
来再申请锁,这在IO调用时很常见,允许其他线程使用处理器而不是等待IO结束。很明显,下面的代码与前面的问题相同:
void
bug(PyObject* list) {
    PyObject* item=PyList_GetItem(list,0);
    Py_BEGIN_ALLOW_THREADS
    //一些IO阻塞调用
    Py_END_ALLOW_THREADS
    PyObject_Print(item,stdout,0); /*BUG*/
}
10.4   NULL指针
一般来说,函数接受的参数并不希望你传递一个NULL指针进来,这会出错的。函数的返回对象引用返回NULL则代表发生了异常。这是Python的机制,毕竟,一个函数如果执行出错了,那么也没有必要多解释了,浪费时间。(注:彪悍的异常也不需要解释)
最好的测试NULL的方法就是在代码里面,一个指针如果收到了NULL,例如 malloc()  或其他函数,则表示发生了异常。
宏 Py_INCREF() 和 Py_DECREF() 并不检查NULL指针,不过还好,  Py_XINCREF() 和 Py_XDECREF() 会检查。
检查特定类型的宏,形如 Pytype_Check() 也不检查NULL指针,因为这个检查是多余的。
C函数的调用机制确保传递的参数列表(也就是args参数)用不为NULL,事实上,它总是一个tuple。
而把NULL扔到Python用户那里可就是一个非常严重的错误了。
11   使用C++编写扩展
有时候需要用C++编写Python扩展模块。不过有一些严格的限制。如果Python解释器的主函数是使用C编译器编译和连接的,那么全局和静态
对象的构造函数将无法使用。而主函数使用C++编译器时则不会有这个问题。被Python调用的函数,特别是模块初始化函数,必须声明为 extern “C” 。没有必要在Python头文件中使用 extern “C” 因为在使用C++编译器时会自动加上 __cplusplus  这个定义,而一般的C++编译器一般都会设置这个符号。
12   提供给其他模块以C  API
很多模块只是提供给Python使用的函数和新类型,但是偶尔也有可能被其他扩展模块所调用。例如一个模块实现了 “collection”
类型,可以像list一样工作而没有顺序。有如标准Python中的list类型一样,提供的C接口可以让扩展模块创建和管理list,这个新的类型也需
要有C函数以供其他扩展模块直接管理。
初看这个功能可能以为很简单:只要写这些函数就行了(不需要声明为静态),提供适当的头文件,并注释C的API。当然,如果所有的扩展模块都是静态
链接到Python解释器的话,这当然可以正常工作。但是当其他扩展模块是动态链接库时,定义在一个模块中的符号,可能对另外一个模块来说并不是可见的。
而这个可见性又是依赖操作系统实现的,一些操作系统对Python解释器使用全局命名空间和所有的扩展模块(例如Windows),也有些系统则需要明确
的声明模块的导出符号表(AIX就是个例子),或者提供一个不同策略的选择(大多数的Unices)。即便这些符号是全局可见的,拥有函数的模块,也可能
尚未载入。
为了可移植性,不要奢望任何符号会对外可见。这意味着模块中所有的符号都声明为 static  ,除了模块的初始化函数以外,这也是为了避免各个扩展模块之间的符号名称冲突。这也意味着必须以其他方式导出扩展模块的符号。
Python提供了一种特殊的机制,以便在扩展模块间传递C级别的信息(指针): CObject
。一个CObject是一个Python的数据类型,存储了任意类型指针(void*)。CObject可以只通过C
API来创建和存取,但是却可以像其他Python对象那样来传递。在特别的情况下,他们可以被赋予一个扩展模块命名空间内的名字。其他扩展模块随后可以
导入这个模块,获取这个名字的值,然后得到CObject中保存的指针。
通过CObject有很多种方式导出扩展模块的C API。每个名字都可以得到他自己的CObject,或者可以把所有的导出C  API放在一个CObject指定的数组中来发布。所以可以有很多种方法导出C API。
如下的示例代码展示了把大部分的重负载任务交给扩展模块,作为一个很普通的扩展模块的例子。他保存了所有的C
API的指针到一个数组中,而这个数组的指针存储在CObject中。对应的头文件提供了一个宏以管理导入模块和获取C
API的指针,客户端模块只需要在存取C API之前执行这个宏就可以了。
这个导出模块是修改自1.1节的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 Py_BuildValue("i",sts);
}
在模块的头部加上如下行:
#include "Python.h"
另外两行需要添加的是:
#define SPAM_MODULE
#include "spammodule.h"
这个宏定义是告诉头文件需要作为导出模块,而不是客户端模块。最终模块的初始化函数必须管理初始化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 CObject containing the API pointer array’s address */
    c_api_object = PyCObject_FromVoidPtr((void *)PySpam_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 and set exception on error, 0 on success. */
static int
import_spam(void)
{
    PyObject *module = PyImport_ImportModule("spam");
    if (module != NULL) {
        PyObject *c_api_object = PyObject_GetAttrString(module, "_C_API");
        if (c_api_object == NULL)
            return -1;
        if (PyCObject_Check(c_api_object))
            PySpam_API = (void **)PyCObject_AsVoidPtr(c_api_object);
        Py_DECREF(c_api_object);
    }
    return 0;
}
#endif
#ifdef __cplusplus
}
#endif
#endif /* !defined(Py_SPAMMODULE_H) */
想要调用 PySpam_System() 的客户端模块必须在初始化函数中调用  import_spam() 以初始化导出扩展模块:
PyMODINIT_FUNC
initclient(void) {
    PyObject* m;
    m=Py_InitModule("client",ClientMethods);
    if (m==NULL)
        return;
    if (import_spam()
这样做的缺点是 spammodule.h 有点复杂。不过这种结构却可以方便的用于其他导出函数,所以学着用一次也就好了。
最后需要提及的是CObject提供的一些附加函数,用于CObject指定的内存块的分配和释放。详细信息可以参考Python的C  API参考手册的CObject一节,和CObject的实现,参考文件 Include/cobject.h 和 Objects/cobject.c 。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值