使用 Python 毋庸置疑減小了不少規則約束和開發成本,讓咱們可以更加專一於邏輯而非語法。可是得此失彼,開發效率提升了,卻帶來了運行性能的問題,因此就經常被其餘門派追着暴打。
身爲一個 pythoner,咱們也很憂傷呀,怪咱們咯..html
萬幸的是,雖然上帝關掉了咱們一扇門,可是卻爲咱們打開了另外一扇窗,正由於底層是用 C語言 寫的,因此咱們能夠將一些性能損耗比較大的功能,或者模塊,經過 C語言 重寫,而後 import xxxx 來無縫結合。python
哪怕工做中比較少機會本身寫C擴展, 瞭解這塊的知識,也有利於咱們更加深刻了解 Python 的運行本質。segmentfault
網上比較是經過 ctypes 或者 setup.py 的方式實現引用和編譯安裝,這邊想試下最原始的方法~api
接口函數是什麼意思?能夠簡單理解成就是 Python 和 C 的對接函數,舉個栗子:app
static PyObject *test(PyObject *self, PyObject *args){ int arg1, arg2; if(!(PyArg_ParseTuple(args, "ii", &arg1, &arg2))){ return NULL; } return Py_BuildValue("i", arg1 + arg2 * 10); }
咱們能夠看到這個函數和傳統意義上的 C 用法用點不一樣了,特別是在函數形參那邊的PyObject self, PyObject argspython2.7
第一個參數是 PyObject *self,這個參數是Python內部使用的,能夠不用管;函數
第二個參數是 PyObject *args,這個參數很是重要,由於這個攬括了全部傳給函數的參數。它是一個參數列表,把全部的參數都整合到post
一個 string, 所以,若是咱們須要解析這些參數須要用特定的姿式!咱們須要用到 PyArg_ParseTuple 來解開這個扣人心絃的入口!性能
PyArg_ParseTuple 函數說明:ui
很明顯,這三個參數,在數量上存在這必定的聯繫,也就是,傳進去兩個 int參數,那麼就確定是對應了兩個 ii,而後就會對應存在 兩個實際的"容器"內,這裏要注意,一不當心就會 Segmentation fault
對應有解析參數的,確定也有 C模塊 值轉換成 Python對象 的,那就是 Py_BuildValue。
Py_BuildValue 函數說明:
# 對比着來看 PyArg_ParseTuple(args, "ii", &arg1, &arg2) Python -> C模塊 Py_BuildValue("i", arg1 + arg2 * 10); C 模塊 -> Python
相關的官方文檔:https://docs.python.org/2/c-a...
# 示例 static PyMethodDef testMethods[] = { {"test", test, METH_VARARGS, "This is test"}, {NULL, NULL, 0, NULL} };
PyMethodDef 是一個 C結構體,用來完成一個映射,也就是便於方法查找,咱們把須要被外面調用的方法都記錄在這表內。
PyMethodDef 結構體成員說明:
須要注意的是,這個列表的最後必須以 {NULL, NULL, 0, NULL} 的形式來表明聲明結束,也有一些大佬用 {NULL, NULL},不過我的以爲寫完整也不會累到哪去, 相反會比較直觀。
# PyMethodDef 結構體定義源碼, 取自 Python2.7 Include/methodobject.h struct PyMethodDef { const char *ml_name; /* The name of the built-in function/method */ PyCFunction ml_meth; /* The C function that implements it */ int ml_flags; /* Combination of METH_xxx flags, which mostly describe the args expected by the C func */ const char *ml_doc; /* The __doc__ attribute, or NULL */ };
正由於存在這樣的一份記錄表,Python 纔可以尋找到相應的函數
一樣的,若是咱們想要找一個模塊的 Python 函數 對應什麼的 C模塊方法,也能經過這地方比較粗暴得知,例如 Python 的 list
# 取自 Python2.7 object/listobject.c static PyMethodDef list_methods[] = { {"__getitem__", (PyCFunction)list_subscript, METH_O|METH_COEXIST, getitem_doc}, {"__reversed__",(PyCFunction)list_reversed, METH_NOARGS, reversed_doc}, {"__sizeof__", (PyCFunction)list_sizeof, METH_NOARGS, sizeof_doc}, {"append", (PyCFunction)listappend, METH_O, append_doc}, {"insert", (PyCFunction)listinsert, METH_VARARGS, insert_doc}, {"extend", (PyCFunction)listextend, METH_O, extend_doc}, {"pop", (PyCFunction)listpop, METH_VARARGS, pop_doc}, {"remove", (PyCFunction)listremove, METH_O, remove_doc}, {"index", (PyCFunction)listindex, METH_VARARGS, index_doc}, {"count", (PyCFunction)listcount, METH_O, count_doc}, {"reverse", (PyCFunction)listreverse, METH_NOARGS, reverse_doc}, {"sort", (PyCFunction)listsort, METH_VARARGS | METH_KEYWORDS, sort_doc}, {NULL, NULL} /* sentinel */ };
PyMODINIT_FUNC inittest(){ Py_InitModule("test", testMethods); }
須要特別注意的是,這個函數名不能像上面那樣,這是有規定的,必須是 init + 模塊名字,比方說,個人最後編譯出來的文件是 test.so, 那個人函數名就是 inittest, 這樣在 Python 導入 test 模塊時,才能找到這個函數並調用。
這裏調用了 Py_InitModule 函數來將模塊名字和映射表結合在一塊兒。表示 test 這個模塊使用 testMethods 這個映射表。
Python 在對象管理、內存管理上面,有引用計數,標記-清除,分代回收等策略,其中引用計數發生的頻率會很是很是高,依賴這個一般已經可以解決大部分的對象殘留問題了。
因此,在咱們編寫 C擴展 時,也須要時刻謹記這步.
主要會用到下面兩個宏:
1. 增長引用: Py_INCREF 例: Py_INCREF(pObj1) 2. 減小引用: Py_DECREF 例: Py_DECREF(pObj1)
不能直接使用 free/delete 釋放,必須使用 Py_DECREF(pObj1), 而後 pObj1 = NULL 便可。
具體能夠參考:
官網:https://docs.python.org/2/ext...
垃圾回收機制: http://www.wklken.me/posts/20...
gcc -I /usr/include/python2.7/ -fpic --shared -o test.so test.c
test.c
#include<Python.h> static PyObject *test(PyObject *self, PyObject *args){ int arg1, arg2; if(!(PyArg_ParseTuple(args, "ii", &arg1, &arg2))){ return NULL; } return Py_BuildValue("i", arg1 + arg2 * 10); } static PyMethodDef testMethods[] = { {"test", test, METH_VARARGS, "This is test"}, {NULL, NULL} }; PyMODINIT_FUNC inittest(){ Py_InitModule("test", testMethods); }
test.py
import test print test.test(1, 2) # 輸出 21
歡迎各位大神指點交流, QQ討論羣: 258498217
轉載請註明來源: https://segmentfault.com/a/11...