歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 快速實現Python C擴展模塊

快速實現Python C擴展模塊

日期:2017/3/1 9:05:42   编辑:Linux編程

1 python擴展模塊的組成

  在python中,對於一些和系統相關的模塊或者對性能要求很高的模塊,通常會把這個模塊C化。擴展模塊中主要包含下面幾個部分:

  • init函數,函數名為:init+模塊名,這個函數負責初始化模塊,包括設置模塊中的方法、對象和其它相關數據的初始化。這個函數是必須的,在腳本中第一次導入這個模塊的時候,會先執行這個方法。
  • 定義模塊方法描述表,它是一個static類型的PyMethodDef數據結構,用來描述模塊中定義的方法。
  • C函數定義,這些函數是方法描述表中方法的具體實現。
  • 如果模塊中定義了類,那麼還要定義類方法描述表和對象類型。

2 實現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庫。

  設置好後直接編譯就可以了,將編譯生成的dll文件後綴名改成pyd,然後就可以在python中直接用import導入這個模塊了。是不是非常的簡單!!!!

3 實現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 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函數。

4 Python/C API涉及的引用計數問題

  通過上面的例子,是不是覺得寫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(如下圖所示),這樣能大大減少引用計數的問題。

5 參考

  python官網

Copyright © Linux教程網 All Rights Reserved