Python C Extension

tags: Python, C

PS. ~夜晚的星空~(StarNight) 原發表於 HiNet Xuite DATE: 06/16/2015 10:02:44 PM

原因

在拜讀完Mosky大大的分享後,

想說除寫 Cython 外,可不可以直接寫 C code 讓 Python 呼叫,畢竟還是慣C阿!
找到一些文章教如何寫 C extesions,例如:Python Extension Programming with C
在 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: 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 將 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)。因為這是極簡的 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

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 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

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 function 處理這樣的動作
      • format: 說明後面要回傳給 Python 的 variable 是哪一種 variable type 的字串。可以參考 Parsing arguments and building values
        • 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 回傳的字串顯示到 console
  • 觀察 helloworld module 的 help

Reference