# Python C Extension ###### tags: `Python`, `C` PS. *\~夜晚的星空~(StarNight)* 原發表於 HiNet Xuite DATE: 06/16/2015 10:02:44 PM ## 原因 在拜讀完Mosky大大的分享後, {%speakerdeck mosky/cython-making-python-as-fast-as-c %} 想說除寫 Cython 外,可不可以直接寫 C code 讓 Python 呼叫,畢竟還是慣C阿! 找到一些文章教如何寫 C extesions,例如:[Python Extension Programming with C](http://www.tutorialspoint.com/python/python_further_extensions.htm)。 在 Python 2.7 的時候,這些教學是 work 的,但我的電腦預設是 Python3 就 GG 了!此時瞬間了解為什麼這麼多的 Python2 libraries 還未支援 Python3 XDD 話雖如此,我還是要在我的電腦上寫 C extensions 阿!所以再翻了一些 Python3 C extensions 的文章,做一下整理~ ## 環境 Archlinux Python 3.4 gcc ## Code https://github.com/starnight/python-c-extension 裡頭有我的階進式練習,最簡單的從不傳參數的 Hello World 開始,接著是傳入參數,與最後的回傳值。 我的規畫: * test.py 是平常寫的 Python 程式,裡頭會要 import 一些 C extension 的 module。 * 這些 module 是用 libxxx.h、libxxx.c 組成的。 * 為了要讓 Python 可以 import 這些 module,就透過 bind.c 用 Python 的 wrapper interface 把 module 包裝起來。 * 實際編譯則是使用 setup.py 的設定與執行,去把 wrapper 好的 Python C extension module 生出來。 * 最後 test.py 就可以 import 這個 Python C extension module。 ### [00-HelloWorld](https://github.com/starnight/python-c-extension/tree/master/00-HelloWorld): Without argument * C extension module: **helloworld** * functions in the module: `hello()` #### libmypy.h * 是 libmypy.c 的 Header file * Include 了 Python 要給 C 用的 Header file * 宣告了一個 hello function,這 function 就是之後要給 test.py 使用的 function #### libmypy.c * **hello function** - 實做了 libmypy.h 宣告的 hello function - 回傳了一個 unicode 字串 * 因為 Python 3 預設的字串是 unicode,所以以 unicode 字串回傳 #### bind.c * hellofunc_docs - 這是hello function要顯示在help的FUNCTIONS說明字串 * helloworld_funcs - 用 [PyMethodDef](https://docs.python.org/3.4/c-api/structures.html#c.PyMethodDef) 將 module 裡的 function 打包起來。因為可能有很多 functions,所以是個 array * ml_name 是之後給 Python 使用的 function name。在這就是 hello 字串 * ml_meth 是這個 function 的進入點,也就是 hello function 的 pointer。在這就是 hello function * ml_flags 是個 flag,告訴 Python 要如何呼叫這個 function。基本上,就是要怎麼把參數傳給 function。在這因為 Python 使用時,不會額外傳參數給 function,所以是 `METH_NOARGS` * ml_doc 是這個 function 的說明,將會出現在 Python help 的 FUNCTIONS 裡。在這就是 hellofunc_docs * helloworldmod_docs - 這是hellowold module要顯示在help的module說明字串 * helloworld_mod - 用PyModuleDef將整個module打包起來。 1. m_base 依據要求,都是設定成 `PyModuleDef_HEAD_INIT`。 2. m_name 是這個 module 的名稱。在這就是 **hellowrold** 3. m_doc 是這個 module 的說明,將會出現在 Python help 的 NAME 裡。在這就是 helloworldmod_docs 4. m_size 是當這個 module re-initialize 時,需要額外多少記憶體(參考[PEP 3121](https://www.python.org/dev/peps/pep-3121/))。因為這是極簡的 module,沒有所謂的 re-initialize。所以在這是`-1`,不會 re-initialize 5. m_methods 是指這 module 有哪些 function 可供 Python 使用。在這就是 helloworld_funcs 的 pointer 6. m_reload 依據要求,目前尚未使用,所以是 NULL。 7. m_traverse 是當 GC 時要 traverse 時,要呼叫的 function。在這不會使用,所以是NULL 8. m_clear 是當 GC 要 clear object 時,要呼叫的 function。在這不會使用,所以是 NULL 9. m_free 是當 module 的 object 被 deallocation 時,要被呼叫的 function。在這不會使用,所以是 NULL * PyInit_helloworld function - 是 helloworld module 的 initialize function - 命名規則是 PyInit_functionname,不依照規則,Python 會找不到 initial 的進入點。 - 呼叫 PyModule_Create 產出一個 helloworld module 給 Python使用。 #### setup.py * Python 佈署 C extension module 的 script * 有關 setup 可以參考 Writing the Setup Script * 但在簡單化的前提下,可以參考 Describing extension modules 的說明 * 因為這 C extension 有 libmypy.c 和 bind.c 要 build,所以在 ext_modules 的 Extension 裡標注 bind.c 和 libmypy.c #### Makefile * 因為是開發使用,不是要裝在 global 系統上。所以在用 python setup 時,加上`build_ext --inplace`參數,讓編譯出來的程式放在原本的路徑下。 #### test.py * 經過上述的動作,就可以 [import helloworld C extension module](https://github.com/starnight/python-c-extension/blob/9ff91b7093e2e400759c8101a4c99bd830f7cf75/00-HelloWorld/test.py#L3) 了。 * [呼叫並將 hello](https://github.com/starnight/python-c-extension/blob/9ff91b7093e2e400759c8101a4c99bd830f7cf75/00-HelloWorld/test.py#L5) 回傳的字串顯示到 console * 也對 helloworld module 做 [help](https://github.com/starnight/python-c-extension/blob/9ff91b7093e2e400759c8101a4c99bd830f7cf75/00-HelloWorld/test.py#L6),看 helloworld module 和 hello function 的說明字串。 ### [01-HeyMan](https://github.com/starnight/python-c-extension/tree/master/01-HeyMan): With passed arguments 除了 00-HelloWorld 提到的部份,還多了 #### libmypy.h、libmypy.c * heyman function - Arguments: * PyObject \*self: 不論是哪一種 module function 都一定會有的參數,指向 module object * PyObject \*args: 指向由 arguments 組成的 tuple object - Return value: * PyObject \*: module function 要將結果回傳給 Python 的物件。如果 return NULL,就代表有 error - 這function會接收從Python傳來的兩個參數: * num (interger) * name (character pointer) - 因為從 Python 傳給 module function 的參數會被打包成 PyObject,所以要用[PyArg_ParseTuple](https://docs.python.org/3.4/c-api/arg.html#c.PyArg_ParseTuple) function 把 args 拆解出要用的參數 * args: 傳入要被拆解的打包過的參數 PyObject 的 pointer * format: 參數 PyObject 拆解出來的參數依序是哪一種 variable type 字串。可以參考 Parsing arguments and building values - i: 整數 - s: 字串 * 後面參數依序是要用 variable 來接拆解出的參數 * 成功回傳 True;失敗回傳 False - 最後將傳入的整數與字串 format、打包後, return 回 Python #### bind.c * 新增 heyman function 的說明字串 heymanfunc_docs * 在 helloworld_funcs 新增 heyman 的 Method Define,設定為有參數要傳入`METH_VARARGS` #### test.py * [呼叫和傳入參數](https://github.com/starnight/python-c-extension/blob/9ff91b7093e2e400759c8101a4c99bd830f7cf75/01-HeyMan/test.py#L6),並將 heyman 回傳的字串顯示到 console * 觀察 helloworld module 的 [help](https://github.com/starnight/python-c-extension/blob/9ff91b7093e2e400759c8101a4c99bd830f7cf75/01-HeyMan/test.py#L7) ### [02-Add](https://github.com/starnight/python-c-extension/tree/master/02-Add): With variables return 除了01-HeyMan提到的部份,還多了 #### libmypy.h、libmypy.c * add function - Arguments: * PyObject \*self: 不論是哪一種 module function 都一定會有的參數,指向 module object * PyObject \*args: 指向由 arguments 組成的 tuple object - Return value: * PyObject \*: module function 要將結果回傳給 Python 的物件。如果 return NULL,就代表有 error。正常預期會回傳由「加法的結果」和「加法的方程式字串」組成的 tuple - 這function會接收從Python傳來的兩個參數: * num1 (interger) 被加數 * num2 (interger) 加數 * 一樣是由 PyArg_ParseTuple function 將 Python 傳入的參數拆解,並存回 num1 和 num2 - 利用 sprintf function,配合被加數 num1、加數 num2 組出加法的方程式字串,並存回 eq character array - 因為要把回傳給 Python 的 variable 打包成 PyObject,所以透過 [Py_BuildValue](https://docs.python.org/3.4/c-api/arg.html#c.Py_BuildValue) function 處理這樣的動作 * format: 說明後面要回傳給 Python 的 variable 是哪一種 variable type 的字串。可以參考 [Parsing arguments and building values](https://docs.python.org/3.4/c-api/arg.html) - i: 整數 - s: 字串 * 後面參數依序是要回傳的 variable * 如果只有 1 個 variable 要回傳,則回傳該 variable type 的 PyObject pointer;若是 2 個以上的 variable 要回傳,則 variables 會先組成 tuple,再打包回傳 * 成功回傳打包好的 PyObject pointer;失敗回傳 NULL #### bind.c * 新增 add function 的說明字串 heymanfunc_docs * 在 helloworld_funcs新增 add 的 Method Define,設定為有參數要傳入`METH_VARARGS` #### test.py * 呼叫和傳入參數,並將 [add](https://github.com/starnight/python-c-extension/blob/9ff91b7093e2e400759c8101a4c99bd830f7cf75/02-Add/test.py#L7) 回傳的字串顯示到 console * 觀察 helloworld module 的 [help](https://github.com/starnight/python-c-extension/blob/9ff91b7093e2e400759c8101a4c99bd830f7cf75/02-Add/test.py#L8) ## Reference * [Python Extension Programming with C](http://www.tutorialspoint.com/python/python_further_extensions.htm) * [Extending Python with C or C++](https://docs.python.org/3.4/extending/extending.html) * [Python.h: No such file or directory](http://unix.stackexchange.com/questions/77940/python-h-no-such-file-or-directory) * [Python.h: No such file or directory](http://lucaswei.blogspot.tw/2012/02/pythonh-no-such-file-or-directory.html) * [Migrating C extensions](http://python3porting.com/cextensions.html) * [Porting Extension Modules to Python 3](https://docs.python.org/3.3/howto/cporting.html)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up