使用 C/C++ 擴展 Python
Python與C/C++互操作有很多方案:Python C API, swig, sip, ctypes, cpython, cffi, boost.python等。這里選擇了最原始的Python C API方式。
一、開發前準備
1.Python對象
大多數Python對象在Python解析器中都為PyObject,在C代碼中只能聲明PyObject*類型的python對象,然后使用該對象對應的初始化函數初始化。如PyTuple_New,PyList_New,PyDict_New,Py_BuildValue等。
例如構建一個{‘a':{‘b':['123','34']}}對象
PyObject* obj = PyDict_New(); PyObject* b = PyDict_New(); PyObject* c = PyList_New(2); PyList_SetItem(c, 0, Py_BuildValue("s", "123")); PyList_SetItem(c, 1, Py_BuildValue("s", "34")); PyDict_SetItem(a, "b", c); PyDict_SetItem(obj, "a", a);
Python對象問題這里有一些文檔:
http://docs.python.org/2/c-api/intro.html#objects-types-and-reference-counts
http://docs.python.org/2/c-api/dict.html
http://docs.python.org/2/c-api/list.html
2.Python內存管理
Python對象管理采用引用技術模型,內部有一些復雜的循環引用等處理措施。主要有 Py_INCREF() / Py_DECREF()兩個宏負責處理。具體文檔可以看這里http://docs.python.org/2/c-api/intro.html#reference-counts
例如上一點申請的對象obj如果需要釋放怎么辦?不可以直接free/delete,直接Py_DECREF(obj),然后obj = NULL即可,否則會報錯。
3.線程安全
Python由于歷史比較悠久,作者在開發的時候可能并沒有考慮到多線程這個東西,因為Python的內存管理并不是線程安全的。在后來后來版本中為了處理這個線程安全問題引入了GIL即global interpreter lock。這是一個粗粒度的鎖,執行Python ByteCode之前都會取得這個鎖。以至于Python的多線程比較雞肋,GIL也就成了性能瓶頸。這個問題很多地方都有討論,我之前有一篇文章專門對這個問題進行了說明,感興趣的同學請去這里http://in.sdo.com/?p=1623。
有人會問為什么不設計更細粒度的鎖?實際上有人已經進行了嘗試,但是為了不增加實現的復雜性也就一直沒有加到CPython中。其他版本的python如IronPython等對這個問題已經做了改善。
實際開發時有兩種情況需要關心:
1).釋放鎖
這種情景只要在進行IO或CPU繁重的計算時,暫時釋放GIL使得其他線程的代碼可以執行。
2).取得鎖
主要出現在C回調Python代碼
參考文檔:
http://docs.python.org/2/c-api/init.html#thread-state-and-the-global-interpreter-lock
二、開發擴展
有了上面的知識我們開始進行實際的開發。
1.導出函數
寫好C API函數之后我們需要導出,寫一個函數描述表即可,如下面的EchoMethods,一定要以NULL結尾。
PyObject* echo(PyObject* self, PyObject* args) { char* input = NULL; if(!PyArg_ParseTuple(args, "s", &input)) { printf("parse arg errorn"); return NULL; } int count = 0; do { printf("%sn", input); count++; }while(count < 100); return Py_BuildValue("i", 0); } static PyMethodDef EchoMethods[] = { {"echo", (PyCFunction)echo, METH_VARARGS}, {NULL, NULL} };
2.導出對象
除了上面提到的使用復雜的PyObject操作語法封裝一個Python對象返回之外還有其他途徑,如直接導出C的Struct到Python。這里不詳談,需要的可以查相關資料。
3.初始化模塊
模塊初始化調用Py_InitModule,傳入模塊名和模塊的方法描述表即可。如果初始化失敗會返回error可以做相應處理。
PyMODINIT_FUNC initecho() { Py_InitModule("echo", EchoMethods); }
三、編譯與使用
1.如何編譯、分發、使用
上面這些代碼當然會用到python-devel庫。編譯的時候使用GCC直接編譯成一般的so,就可以直接在python里面調用了。Python會自己選擇如何加載這個so。
g++ -c echo.c -I /usr/include/python2.7/include/python2.7 -fPIC g++ -shared echo.o -o echo.so
上面已經提到了,實際上把自己編譯好的so放在PYTHONPATH路徑中的任意一個下面都可以直接調用了。
2.更便捷的方式
上面的編譯方式可以自己寫一個Makefile處理起來更靈活,實際上Python有一個更方便的處理方式。使用distutils包,編譯安裝一步到位,這也是easy_install等工具使用的方式。
上面這個簡單使用distutils處理起來像這樣:
from distutils.core import setup, Extension echomodule = Extension("echo", sources = ["echo.c"]) setup(name = "echo", version = "1.0", description = "test", author = "dudu" ext_modules = [echomodule])
Extension對象定義一個擴展的源文件、需要用到的第三方庫、頭文件、特殊的編譯選項等等,而setup則定義安裝的規則及擴展的一些屬性。
使用的時候執行下面兩個命令就可以了。
python setup.py build sudo python setup.py install
這部分可以參考http://docs.python.org/2/distutils/apiref.html
文章是寫完了。特別推薦需要開發許多接口的人去看看開頭提到的swig/sip等等,這些項目只需要編寫簡單的規則,就可以為c/c++中的方法生成wrapper。這里只所以有采用c api是因為需求簡單,需要暴露給python的總共也沒幾個函數。
作者:麥田守望
就職于盛大創新院,主要從事搜索引擎研發等工作。熟悉C/C++,Python,Node.JS
來自:http://www.the520.cn/2014/02/27/python_c_api_extension.htm