歡迎光臨
每天分享高質量文章

用C語言武裝Python,讓程式碼執行速度飛起來

導讀:眾所周知,作為解釋型語言的 Python 可不是什麼超級快速的語言,但許多複雜的庫函式(比如 NumPy 庫)卻能執行得相當快速。這主要是因為這些庫的核心程式碼往往是用 C 或者 C++ 寫好,並經過了編譯,比解釋執行的 Python 程式碼有更快的執行速度。

在這篇短文中,我們將詳細聊一聊如何用 C 或者 C++ 寫一個 Python 模組(或軟體包),內容主要參考 Python 官方檔案。作為範例,我也將用 C 寫一個簡單的 Python 模組,完成一個簡單的數學計算: n!=n×(n-1)×(n-2)… 。

為了實現上面的標的,我們需要兩個檔案:一個 Python 程式碼 setup.py以及我們實際編寫的 C 語言程式碼 cmath.c

總的來說,我們將用 setup.py 把 C 語言寫的程式碼 cmath.c 構建成一個 Python 庫(這其中包括編譯程式碼、查詢 Python C 庫、連線等操作)。

那麼,讓我們開始吧!

 

作者:Matthias Bitzer

編譯:歐剃

來源:優達學城Udacity(ID:youdaxue)

原文:medium.com/@matthiasbit

01 原理

為了讓我們的程式/模組能在 Python 程式碼中被呼叫執行,模組需要和 Python 直譯器 CPython 進行必要的通訊。因此,我們需要 Python.h頭檔案裡面的若干物件,並用它們構建出合適的結構體。

基本上,我們要做的是把實際的 C 語言方法包裝起來,以便能夠被 Python 直譯器所呼叫,這樣我們的 Python 程式碼才能夠像使用普通的 Python 函式一樣,呼叫這個方法。

02 編寫演演算法並包裝

首先,我們要在 cmath.c 裡引入頭檔案:

#include Python.h

在 Python 頭檔案裡,我們需要用來和 Python 直譯器對接的物件(以及函式),都以 Py 開頭。在這裡,能代表所有 python 物件的 C 物件(基本上就是一個opaque——“不透明”物件)叫做 PyObject

不過,在實際使用這些物件之前,我們先把求階乘的演演算法寫出來(註意,0的階乘是1):

int fastfactorial(int n){
 if(n<=1)
 return 1;
 else
 return n * fastfactorial(n-1);
}

接著,我們給這個函式進行一下包裝。這個包裹函式接收一個 PyObject 型別的指標(指向今後從 Python 程式碼傳入的引數)作為引數,再傳回一個 PyObject 型別的指標(指向上面函式的傳回值)給外部。

為此,我們用以下程式碼來實現這個包裹函式:

static PyObject* factorial(PyObject* self, PyObject* args){
int n;
if (!PyArg_ParseTuple(args,"i",&n;))
  return NULL;
int result = fastfactorial(n);
return Py_BuildValue("i",result);
}

這個函式始終需要一個指向模組物件本身的 self 指標,以及一個指向從 Python 程式碼傳入引數的 args 指標(二者都是 PyObject 型別的物件)。我們用 PyArg_ParseTuple 方法來處理這些引數,並且宣告我們需要的是整數型別(第二個引數 "i"),最後將處理結果賦值到變數 n 中。

接著自然是呼叫 fastfactorial(n) 來計算階乘,並用 Python 頭檔案裡的 Py_BuildValue 方法把傳回值塞回 PyObject* 型別裡。最後,我們的包裹函式將指向結果的指標物件傳回給外部。

03 組裝模組結構

現在,我們已經把實際的階乘函式封裝完畢,接下來需要構造一個 PyModuleDef 結構體的實體(這個物件也是由 Python.h 所定義的。這個結構體定義了模組的結構,以便 Python 直譯器載入呼叫。

而模組的另一個組成部分是定義它的所有方法,這由另一個結構體 PyMethodDef 實現——它其實就相當於一個陣列,裡面列出了模組中所有的方法和對應的說明。

在當前例子中,我們定義瞭如下的 PyMethodDef 物件:

static PyMethodDef mainMethods[] = {
 {"factorial",factorial,METH_VARARGS,"Calculate the factorial of n"},
 {NULL,NULL,0,NULL}
};

這個物件裡目前共有 2 個元素——我們在最末尾加入了一個由 NULL 組成的結構體,做為結尾。第 0 個物件是我們定義的方法,它的結構是:先是方法名 factorial,其次是實際呼叫的函式物件,註意這裡呼叫的是上一節定義的包裹函式;接下來指定了這個方法是從 METH_VARARGS 這個常量中獲得它的引數;最後是一個說明字串。

於是,我們已經定義了這個 Python 模組中的所有方法(本例中就一個),我們可以建立一個 PyModuleDef 的實體,作為代表整個 Python 模組的物件。

程式碼如下:

static PyModuleDef cmath = {
 PyModuleDef_HEAD_INIT,
 "cmath","Factorial Calculation",
 -1,
 mainMethods
};

在上面的程式碼中,我們首先定義了模組名 cmath 以及簡短的檔案字串,然後再把所有的方法組成的陣列 mainMethods 放進去。

最後一步,我們要新增一個函式,並讓 python 程式碼匯入這個模組的時候執行這個函式。

程式碼如下:

PyMODINIT_FUNC PyInit_cmath(void){
 return PyModule_Create(&cmath;);
}

函式的傳回型別是 PyMODINIT_FUNC,這表明函式實際上傳回的是一個 PyObject 型別的指標。這個指標指向由 PyModule_Create 生成的 Python 模組本身(這個模組物件本身也是一個 PyObject物件)。當一個模組被 Python 程式碼匯入時,這個方法就會被呼叫,並傳回一個指向整個模組物件,包含了所有方法的指標。

04 編譯打包模組

現在我們的 C 程式碼檔案已經準備好了,所有的方法都已經包裝到位,Python 直譯器匯入、執行所需的結構體也已經定義完善。於是,我們可以開始構建最終的二進位制檔案了。

在這個過程中,我們的 C 程式碼需要被編譯、並和正確的庫檔案連線(本例中,我們用到的主要是 Python 頭檔案中定義的那些方法和物件)。為了簡化構建過程,我們可以用到 distutils.core 模組裡的 setup和 Extension 方法。

簡單地說,這兩個方法基本上能搞定整個構建過程。我們只要把 setup.py 和 cmath.c 放在同一個檔案夾裡,然後引入這兩個方法即可。

這是完整的 setup.py 檔案內容:

from distutils.core import setup, Extension
factorial_module = Extension('cmath',sources = ['cmath.c'])
setup(name = 'MathExtension',
      version='1.0',
      description = 'This is a math package',
      ext_modules = [factorial_module]
     )

在上面的程式碼中,我們首先宣告了 factorial_module 變數,作為一個 C 語言擴充套件物件,原始碼 source 來自我們的 C 程式碼檔案。這一行基本就是告訴 setup 方法要編譯的源檔案是哪個。

接下來,我們呼叫 setup() 函式,這個函式接收的引數就是將來要構建的包名(MathExtension)、版本號(1.0)、簡短的描述檔案,以及要包括在內的 C 語言擴充套件/模組物件( factorial_module )。這樣,setup.py就寫好了,是不是很簡單?

最後,我們執行一下 setup.py。執行時可以選擇兩種不同的樣式。如果是 build,程式就只編譯這個模組(一個 .so 格式的庫檔案)並把編譯結果放在當前檔案夾裡的 build 子檔案夾內;如果是 install,則會將編譯結果放在 python 的環境變數 PATH 指向的檔案夾裡,以便其他程式呼叫。

今天的例子裡,我們選擇 build 選項。在終端/命令提示符裡輸入以下命令:

python setup.py build

如果一切正常,你就會在當前檔案夾裡看到一個 build 檔案夾,併在裡面看到編譯出來的 .so 檔案。這個庫檔案可以被 Python 指令碼呼叫,並執行我們用 C 編寫的階乘函式。

05 測試結果

讓我們試一下吧。我簡單地寫了一個 test.py,並把它放在和 .so 檔案同一個檔案夾下,方便呼叫(當然,你如果用了 install 選項,那就無需這麼做,在任意目錄都能呼叫這個包)。

test.py 檔案的內容如下:

 

from cmath import factorial
print(factorial(6))

執行一下,得到結果 720。搞定!我們用 C 語言寫的這個小模組成功地匯入並執行啦!

恭喜你已經看完了今天的小教程,你打算給自己的 python 增加哪些威力強大的模組呢?

    已同步到看一看
    贊(0)

    分享創造快樂