在python中,對於一些和系統相關的模塊或者對性能要求很高的模塊,一般會把這個模塊C化。擴展模塊中主要包含下面幾個部分:html
固然,有了上面的組成部分,你仍是不知道怎麼實現一個模塊,下面就用官方的一個例子來演示怎麼實現一個python擴展模塊,這個擴展模塊用來實如今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庫。c++
設置好後直接編譯就能夠了,將編譯生成的dll文件後綴名改爲pyd,而後就能夠在python中直接用import導入這個模塊了。是否是很是的簡單!!!!shell
上面的實現是在模塊中定義函數來實現執行命令行命令,咱們也能夠在模塊中定義類,而後用類的方法來執行這個命令。代碼以下:api
// 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()來釋放這個引用,典型的函數是
appPyObject_、
PyNumber_
、PySequence_和
PyMapping_
;borrow reference與new reference恰好相反,表示函數的調用者只管用這個引用,不用關心它的引用計數,用完了也不用顯示調用Py_DECREF()或者
Py_XDECREF()來釋放這個引用,典型的函數是PyList_GetItem、PyTuple_GetItem
;steal reference表示函數內部只會使用這個引用,不會調用Py_INCREF來增長這個引用的引用計數,至關於「偷了」被調用者的一個引用計數,典型的函數是PyList_SetItem()和
。所以,在編寫C擴展的時,若是遇到某個Python/C API不肯定是哪一種reference的時候,建議查下官方文檔,文檔中會明確的說明這個函數是哪類reference(以下圖所示),這樣能大大減小引用計數的問題。PyTuple_SetItem()
python官網函數