在python中,对于一些和系统相关的模块或者对性能要求很高的模块,通常会把这个模块C化。扩展模块中主要包含下面几个部分:
? 当然,有了上面的组成部分,你还是不知道怎么实现一个模块,下面就用官方的一个例子来演示怎么实现一个python扩展模块,这个扩展模块用来实现在python中执行命令行命令。
? 上面的initspam是模块的初始化函数,函数开始调用了Py_InitModule初始化了一个名为spam的模块,模块的方法描述表是SpamMethods,它描述了模块有个名为system的方法,这个方法的c/c++实现是spam_system函数。从spam_system函数可以看到它就是调用system函数执行从python传过来的命令。有了上面的代码,我们怎样在python中使用了?很简单,先将上面代码编译成动态链接库,然后直接在python中用import语句导入这个模块就可以用了。在Windows下的用vs编译就行,不过在vs建立了dll工程后,需要设置下工程的属性,目的是设置python扩展涉及到的头文件路径和动态库。具体设置如下:先在VC++目录中设置include和lib路径,然后在链接器的附加依赖项中添加python27.lib库。
设置好后直接编译就可以了,将编译生成的dll文件后缀名改成pyd,然后就可以在python中直接用import导入这个模块了。是不是非常的简单!!!!
上面的实现是在模块中定义函数来实现执行命令行命令,我们也可以在模块中定义类,然后用类的方法来执行这个命令。代码如下:
上面的代码与之前的代码只是多了个Spam类的定义,使用的时候通过Spam的实例化对象来调用system函数。
通过上面的例子,是不是觉得写python的C扩展模块非常的简单呢?其实不然,主要是python中有个引用计数问题,在写扩展模块的时候必须非常小心的处理,否则很有容易导致内存泄露。根据python官方的定义,在Python/C API中,引用计数的行为被归纳为三种:new reference、borrow reference和steal reference,前两种用于描述返回PyObject*类型的函数对返回的这个对象的引用计数的行为;后一种用于将一个PyObject*类型传入函数后,函数对这个对象的引用计数的行为。new referenc表示函数将这个对象引用的所有权转交给函数调用者了,由函数的调用者来管理这个引进的计数,也就是说调用者不用这个引用的时候必须显示的调用?Py_DECREF()或者
Py_XDECREF()来释放这个引用,典型的函数是PyObject_、
PyNumber_
、PySequence_和
PyMapping_
;borrow reference与new reference刚好相反,表示函数的调用者只管用这个引用,不用关心它的引用计数,用完了也不用显示调用Py_DECREF()或者
Py_XDECREF()来释放这个引用,典型的函数是PyList_GetItem、PyTuple_GetItem
;steal reference表示函数内部只会使用这个引用,不会调用Py_INCREF来增加这个引用的引用计数,相当于“偷了”被调用者的一个引用计数,典型的函数是PyList_SetItem()和PyTuple_SetItem()
。因此,在编写C扩展的时,如果遇到某个Python/C API不确定是哪种reference的时候,建议查下官方文档,文档中会明确的说明这个函数是哪类reference(如下图所示),这样能大大减少引用计数的问题。
python官网