用 C 語言武裝 Python ,讓代碼執(zhí)行速度飛起來!

眾所周知,作為解釋型語言的 Python 可不是什么超級快速的語言,但許多復(fù)雜的庫函數(shù)(比如?NumPy?庫)卻能執(zhí)行得相當(dāng)快速。這主要是因為這些庫的核心代碼往往是用 C 或者 C++ 寫好,并經(jīng)過了編譯,比解釋執(zhí)行的 Python 代碼有更快的執(zhí)行速度。

在這篇短文中,我們將詳細(xì)聊一聊如何用 C 或者 C++ 寫一個 Python 模組(或軟件包),內(nèi)容主要參考 Python 官方文檔。作為范例,我也將用 C 寫一個簡單的 Python 模組,完成一個簡單的數(shù)學(xué)計算:?n!=n×(n-1)×(n-2)… 。為了實現(xiàn)上面的目標(biāo),我們需要兩個文件:一個 Python 代碼?setup.py,以及我們實際編寫的 C 語言代碼?cmath.c。

總的來說,我們將用?setup.py?把 C 語言寫的代碼?cmath.c?構(gòu)建成一個 Python 庫(這其中包括編譯代碼、查找 Python C 庫、連接等操作)。

那么,讓我們開始吧!


原理

為了讓我們的程序/模組能在 Python 代碼中被調(diào)用執(zhí)行,模組需要和 Python 解釋器?CPython?進(jìn)行必要的通訊。因此,我們需要?Python.h?頭文件里面的若干對象,并用它們構(gòu)建出合適的結(jié)構(gòu)體。

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

編寫算法并包裝

首先,我們要在?cmath.c?里引入頭文件:

#include Python.h

在 Python 頭文件里,我們需要用來和 Python 解釋器對接的對象(以及函數(shù)),都以?Py?開頭。在這里,能代表所有 python 對象的 C 對象(基本上就是一個opaque——“不透明”對象)叫做?PyObject。

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

int fastfactorial(int n){

if(n<=1)

return 1;

else

return n * fastfactorial(n-1);

}

接著,我們給這個函數(shù)進(jìn)行一下包裝。這個包裹函數(shù)接收一個 PyObject 類型的指針(指向今后從 Python 代碼傳入的參數(shù))作為參數(shù),再返回一個 PyObject 類型的指針(指向上面函數(shù)的返回值)給外部。

為此,我們用以下代碼來實現(xiàn)這個包裹函數(shù):

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

}

這個函數(shù)始終需要一個指向模組對象本身的?self?指針,以及一個指向從 Python 代碼傳入?yún)?shù)的?args?指針(二者都是?PyObject?類型的對象)。我們用?PyArg_ParseTuple?方法來處理這些參數(shù),并且聲明我們需要的是整數(shù)類型(第二個參數(shù)?"i"),最后將處理結(jié)果賦值到變量?n?中。

接著自然是調(diào)用?fastfactorial(n)?來計算階乘,并用 Python 頭文件里的?Py_BuildValue?方法把返回值塞回?PyObject*?類型里。最后,我們的包裹函數(shù)將指向結(jié)果的指針對象返回給外部。


組裝模組結(jié)構(gòu)

現(xiàn)在,我們已經(jīng)把實際的階乘函數(shù)封裝完畢,接下來需要構(gòu)造一個?PyModuleDef?結(jié)構(gòu)體的實例(這個對象也是由?Python.h?所定義的。這個結(jié)構(gòu)體定義了模組的結(jié)構(gòu),以便 Python 解釋器載入調(diào)用。而模組的另一個組成部分是定義它的所有方法,這由另一個結(jié)構(gòu)體?PyMethodDef?實現(xiàn)——它其實就相當(dāng)于一個數(shù)組,里面列出了模組中所有的方法和對應(yīng)的說明。

在當(dāng)前例子中,我們定義了如下的?PyMethodDef?對象:

static PyMethodDef mainMethods[] = {

{"factorial",factorial,METH_VARARGS,"Calculate the factorial of n"},

{NULL,NULL,0,NULL}

};

這個對象里目前共有 2 個元素——我們在最末尾加入了一個由?NULL?組成的結(jié)構(gòu)體,做為結(jié)尾。第 0 個對象是我們定義的方法,它的結(jié)構(gòu)是:先是方法名?factorial,其次是實際調(diào)用的函數(shù)對象,注意這里調(diào)用的是上一節(jié)定義的包裹函數(shù);接下來指定了這個方法是從?METH_VARARGS?這個常量中獲得它的參數(shù);最后是一個說明字符串。

于是,我們已經(jīng)定義了這個 Python 模組中的所有方法(本例中就一個),我們可以創(chuàng)建一個?PyModuleDef?的實例,作為代表整個 Python 模組的對象。

代碼如下:

static PyModuleDef cmath = {

PyModuleDef_HEAD_INIT,

"cmath","Factorial Calculation",

-1,

mainMethods

};

在上面的代碼中,我們首先定義了模組名?cmath?以及簡短的文檔字符串,然后再把所有的方法組成的數(shù)組?mainMethods?放進(jìn)去。

最后一步,我們要添加一個函數(shù),并讓 python 代碼導(dǎo)入這個模組的時候執(zhí)行這個函數(shù)。

代碼如下:

PyMODINIT_FUNC PyInit_cmath(void){

return PyModule_Create(&cmath);

}

函數(shù)的返回類型是?PyMODINIT_FUNC,這表明函數(shù)實際上返回的是一個?PyObject?類型的指針。這個指針指向由?PyModule_Create?生成的 Python 模組本身(這個模組對象本身也是一個?PyObject對象)。當(dāng)一個模組被 Python 代碼導(dǎo)入時,這個方法就會被調(diào)用,并返回一個指向整個模組對象,包含了所有方法的指針。


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


編譯打包模組

現(xiàn)在我們的 C 代碼文件已經(jīng)準(zhǔn)備好了,所有的方法都已經(jīng)包裝到位,Python 解釋器導(dǎo)入、執(zhí)行所需的結(jié)構(gòu)體也已經(jīng)定義完善。于是,我們可以開始構(gòu)建最終的二進(jìn)制文件了。在這個過程中,我們的 C 代碼需要被編譯、并和正確的庫文件連接(本例中,我們用到的主要是?Python?頭文件中定義的那些方法和對象)。為了簡化構(gòu)建過程,我們可以用到?distutils.core?模組里的?setup和?Extension?方法。

簡單地說,這兩個方法基本上能搞定整個構(gòu)建過程。我們只要把?setup.py?和?cmath.c?放在同一個文件夾里,然后引入這兩個方法即可。

這是完整的?setup.py?文件內(nèi)容:

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 語言擴(kuò)展對象,源代碼?source?來自我們的 C 代碼文件。這一行基本就是告訴 setup 方法要編譯的源文件是哪個。

接下來,我們調(diào)用?setup()?函數(shù),這個函數(shù)接收的參數(shù)就是將來要構(gòu)建的包名(?MathExtension)、版本號(1.0)、簡短的描述文檔,以及要包括在內(nèi)的 C 語言擴(kuò)展/模組對象(?factorial_module?)。這樣,setup.py?就寫好了,是不是很簡單?

最后,我們運行一下?setup.py。運行時可以選擇兩種不同的模式。如果是?build,程序就只編譯這個模塊(一個?.so?格式的庫文件)并把編譯結(jié)果放在當(dāng)前文件夾里的 build 子文件夾內(nèi);如果是?install,則會將編譯結(jié)果放在 python 的環(huán)境變量 PATH 指向的文件夾里,以便其他程序調(diào)用。

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

python setup.py build

如果一切正常,你就會在當(dāng)前文件夾里看到一個?build?文件夾,并在里面看到編譯出來的?.so?文件。這個庫文件可以被 Python 腳本調(diào)用,并執(zhí)行我們用 C 編寫的階乘函數(shù)。


測試結(jié)果

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

test.py?文件的內(nèi)容如下:

from cmath import factorial

print(factorial(6))

運行一下,得到結(jié)果 720。搞定!我們用 C 語言寫的這個小模組成功地導(dǎo)入并執(zhí)行啦!

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容