在 python 中,对于一些和系统相关的模块或者对性能要求很高的模块,通常会把这个模块 C 化。扩展模块中主要包含下面几个部分:
当然,有了上面的组成部分,你还是不知道怎么实现一个模块,下面就用官方的一个例子来演示怎么实现一个 python 扩展模块,这个扩展模块用来实现在 python 中执行命令行命令。
- // spam.c
- 1#include "Python.h"2 3 static PyObject * SpamError;
- 4 5 static PyObject * 6 spam_system(PyObject * self, PyObject * args) 7 {
- 8 const char * command;
- 9 int sts;
- 10 11
- if (!PyArg_ParseTuple(args, "s", &command)) 12
- return NULL;
- 13 sts = system(command);
- 14
- if (sts < 0) {
- 15 PyErr_SetString(SpamError, "System command failed");
- 16
- return NULL;
- 17
- }
- 18
- return PyLong_FromLong(sts);
- 19
- }
- 20 21 static PyMethodDef SpamMethods[] = {
- 22 {
- "system",
- spam_system,
- METH_VARARGS,
- 23 "Execute a shell command."
- },
- 24 {
- NULL,
- NULL,
- 0,
- NULL
- }
- /* Sentinel */
- 25
- };
- 26 27 PyMODINIT_FUNC 28 initspam(void) 29 {
- 30 PyObject * m;
- 31 32 m = Py_InitModule("spam", SpamMethods);
- 33
- if (m == NULL) 34
- return;
- 35 36 SpamError = PyErr_NewException("spam.error", NULL, NULL);
- 37 Py_INCREF(SpamError);
- 38 PyModule_AddObject(m, "error", SpamError);
- 39
- }
上面的 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.c
- 1#include "Python.h"2 3 static PyObject * SpamError;
- 4 5 static PyObject * 6 spam_system(PyObject * self, PyObject * args) 7 {
- 8 const char * command;
- 9 int sts;
- 10 11
- if (!PyArg_ParseTuple(args, "s", &command)) 12
- return NULL;
- 13 sts = system(command);
- 14
- if (sts < 0) {
- 15 PyErr_SetString(SpamError, "System command failed");
- 16
- return NULL;
- 17
- }
- 18
- return PyLong_FromLong(sts);
- 19
- }
- 20 21 static PyMethodDef SpamMethods[] = {
- 22 {
- "system",
- spam_system,
- METH_VARARGS,
- 23 "Execute a shell command."
- },
- 24 {
- NULL,
- NULL,
- 0,
- NULL
- }
- /* Sentinel */
- 25
- };
- 26 27 PyTypeObject * SpamType = NULL;
- 28 29 PyMODINIT_FUNC 30 initspam(void) 31 {
- 32 static PyTypeObject _SpamType = {
- 33 PyObject_HEAD_INIT(NULL) 34 0,
- // ob_size
- 35 "spam.Spam",
- // tp_name
- 36 sizeof(PyObject),
- // tp_basicsize
- 37 0,
- // tp_itemsize
- 38 0,
- // tp_dealloc
- 39 0,
- // tp_print
- 40 0,
- // tp_getattr
- 41 0,
- // tp_setattr
- 42 0,
- // tp_compare
- 43 0,
- // tp_repr
- 44 0,
- // tp_as_number
- 45 0,
- // tp_as_sequence
- 46 0,
- // tp_as_mapping
- 47 0,
- // tp_hash
- 48 0,
- // tp_call
- 49 0,
- // tp_str
- 50 0,
- // tp_getattro
- 51 0,
- // tp_setattro
- 52 0,
- // tp_as_buffer
- 53 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
- // tp_flags
- 54 0,
- // tp_doc
- 55 0,
- // tp_traverse
- 56 0,
- // tp_clear
- 57 0,
- // tp_richcompare
- 58 0,
- // tp_weaklistoffset
- 59 0,
- // tp_iter
- 60 0,
- // tp_iternext
- 61 SpamMethods,
- // tp_methods
- 62 0,
- // tp_members
- 63 0,
- // tp_getset
- 64 0,
- // tp_base
- 65 0,
- // tp_dict
- 66 0,
- // tp_descr_get
- 67 0,
- // tp_descr_set
- 68 0,
- // tp_dictoffset
- 69 0,
- // tp_init
- 70 0,
- // tp_alloc
- 71 PyType_GenericNew,
- // tp_new
- 72
- };
- 73 74 PyObject * m;
- 75 76 m = Py_InitModule("spam", NULL);
- 77
- if (m == NULL) 78
- return;
- 79
- if (PyType_Ready( & _SpamType) < 0) 80
- return;
- 81 SpamType = &_SpamType;
- 82 Py_INCREF(SpamType);
- 83 PyModule_AddObject(m, "Spam", (PyObject * ) SpamType);
- 84 SpamError = PyErr_NewException("spam.error", NULL, NULL);
- 85 Py_INCREF(SpamError);
- 86 PyModule_AddObject(m, "error", SpamError);
- 87
- }
上面的代码与之前的代码只是多了个 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(如下图所示),这样能大大减少引用计数的问题。
来源: