用 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 庫、連接等操作)。

那么,讓我們開始吧!


原理

為了讓我們的程序/模組能在 Python 代碼中被調用執行,模組需要和 Python 解釋器?CPython?進行必要的通訊。因此,我們需要?Python.h?頭文件里面的若干對象,并用它們構建出合適的結構體。

基本上,我們要做的是把實際的 C 語言方法包裝起來,以便能夠被 Python 解釋器所調用,這樣我們的 Python 代碼才能夠像使用普通的 Python 函數一樣,調用這個方法。

編寫算法并包裝

首先,我們要在?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*?類型里。最后,我們的包裹函數將指向結果的指針對象返回給外部。


組裝模組結構

現在,我們已經把實際的階乘函數封裝完畢,接下來需要構造一個?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 代碼導入時,這個方法就會被調用,并返回一個指向整個模組對象,包含了所有方法的指針。


小編給大家推薦一個學習氛圍超好的地方,C/C++交流企鵝裙:【870+963+251】適合在校大學生,小白,想轉行,想通過這個找工作的加入。裙里有大量學習資料,有大神解答交流問題,每晚都有免費的直播課程


編譯打包模組

現在我們的 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 編寫的階乘函數。


測試結果

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

test.py?文件的內容如下:

from cmath import factorial

print(factorial(6))

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

恭喜你已經看完了今天的小教程,你打算給自己的 python 增加哪些威力強大的模塊呢?歡迎留言吐槽!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • C++調用python 在C/C++中嵌入Python,可以使用Python提供的強大功能,通過嵌入Python可...
    Bruce_Szh閱讀 13,826評論 1 7
  • Python是一門功能強大的高級腳本語言,它的強大不僅表現在其自身的功能上,而且還表現在其良好的可擴展性上,正因如...
    蝴蝶蘭玫瑰閱讀 1,644評論 0 17
  • Distutils可以用來在Python環境中構建和安裝額外的模塊。新的模塊可以是純Python的,也可以...
    MiracleJQ閱讀 3,126評論 0 1
  • Python語言特性 1 Python的函數參數傳遞 看兩個如下例子,分析運行結果: 代碼一: a = 1 def...
    伊森H閱讀 3,083評論 0 15
  • 直接高潮,我會從5個方面告訴你,為什么庫里才是勇士隊老大? 1.勇士是近三個賽季才從西部脫穎而出。12-13賽季,...
    籃球白癡閱讀 582評論 0 1