Python通過ctypes呼叫C程式例項

發表於2017-01-12

在做複雜計算時,Python的執行效率是無法同C比的。而且有些演算法已經有開源的C庫了,我們也沒必要用Python重寫一份。那Python怎麼呼叫C語言寫的程式庫呢?答案就在Python提供的ctypes庫,它提供同C語言相容的資料型別,可以很方便地呼叫C語言動態連結庫中的函式。

使用C標準庫函式

我們來試試呼叫C標準庫函式:

上述程式碼載入了Linux中的C標準庫”libc.so.6″,並呼叫其中”time()”函式,執行後螢幕上會列印出當前時間戳。注,Windows和Mac上的載入方法在註釋中。

呼叫自定義的動態連結庫

我們先根據這篇文章寫個動態連結庫,現在你有了庫”libhello.so”,其有一個hello函式。讓我們在Python中呼叫它:

看到螢幕上”Hello You!”的字樣了吧。對!就這麼簡單,比起Java呼叫原生程式碼方便很多吧。注意,本例中的”libhello.so”同Python程式在同一目錄下。

效率對比

我們寫個階乘(factorial)函式,來比較Python和C的執行效率吧。先寫C程式碼:

方便起見,我們把它放在之前寫的”hello.c”檔案中,這樣就可以從”libhello.so”中呼叫它。別忘了在”hello.h”中宣告這個函式。然後實現Python程式碼:

Python的實現可以說同C的一模一樣,我們另外定義一個”factorial_c()”函式來封裝C的呼叫。現在,我們來比較下執行效率。這裡要引入Python的”timeit”包,它可以幫你計算程式的執行時間,省去你很多程式碼。讓我們來算20的階乘,並計算10萬次,看看所消耗的時間:

我在虛擬機器上跑的結果結果是:

差不多5倍的差距啊!

引數傳址

大家知道C的函式引數是傳值的(其實Python也一樣),那我想在C中改變引數的值怎麼辦,那就需要傳遞引用了。我們在上面的”libhello.so”中加一個快排函式:

朋友們馬上可以看出,這段函式中陣列a中的值是可以被改變的。那Python怎麼呼叫它呢?就是在引數傳遞時,加上”byref()”呼叫,它是ctypes提供的方法,如果用它呼叫int型變數a時,作用類似於”(int *) &a”。所以我們的Python程式可以這樣寫:

這裡還有個知識點,就是C型別。為了同C的變數型別相容,ctypes庫提供了一系列對應的C型別。本例中c_int就是對應C中的int型。我們將”c_int * 10″就等於建立一個長度為10的int型陣列。而後面的(*number)就是把numbers的值賦給剛建立的int陣列。ctypes庫所有提供的C型別可以在這裡查到

上例中,我們必須傳入C型別的整型陣列才能被C程式接收。現在讓我們來使用下這個快排:

有興致的朋友們也可以寫個Python的快排來比較下效率。

引數及返回型別指定

我們回到C標準庫,呼叫下”strchr”方法,它的作用是在字串中找出以指定字元開頭的子串。

你會發現,返回一直是0,而我們期望的應該是”def”。其實,問題是在我們的第二個引數,它應該是一個字元,而Python中它是一個字串。那怎麼讓它成為字元型別呢?一個方法是使用”strchr(‘abcdef’, ord(‘d’))”呼叫”strchr”方法,”ord()”函式可以把字串變成字元型別,但是每次呼叫都要加上,很麻煩。還有一個辦法就是指定函式輸入引數的型別。我們可以加上程式碼:

函式的”argtypes”屬性就可以指定傳入引數的型別。這裡,第一個引數是字元指標,也就是C中的字串,第二個是字元。

我們再來執行下程式,奇怪,雖然有返回了,但一直是一個長整型數值,為什麼呢?瞭解’strchr’的朋友們應該知道,這個函式返回的是”char *”型別,它是一個字元指標,所以你在Python中獲取的那個數值,就是指標的地址。那要怎麼把指標轉為字串呢?也很簡單,通過函式的”restype”屬性指定返回值型別即可。完整的程式如下:

關於ctypes庫的更詳細內容可以參考Python官方文件

文中的示例程式碼可以從這裡下載

相關文章