您好,登錄后才能下訂單哦!
事先聲明,標題沒有把“Python”錯打成“Cython”,因為要講的就是名為“Cython”的東西。
Cython是讓Python腳本支持C語言擴展的編譯器,Cython能夠將Python+C混合編碼的.pyx腳本轉換為C代碼,主要用于優化Python腳本性能或Python調用C函數庫。由于Python固有的性能差的問題,用C擴展Python成為提高Python性能常用方法,Cython算是較為常見的一種擴展方式。
我們可以對比一下業界主流的幾種Python擴展支持C語言的方案:
有試用版水印,是因為窮T_T
ctypes是Python標準庫支持的方案,直接在Python腳本中導入C的.so庫進行調用,簡單直接。swig是一個通用的讓高級腳本語言擴展支持C的工具,自然也是支持Python的。ctypes沒玩過,不做評價。以c語言程序性能為基準的話,cython封裝后下降20%,swig封裝后下降70%。功能方面,swig對結構體和回調函數都要使用typemap進行手工編寫轉換規則,typemap規則寫起來略復雜,體驗不是很好。cython在結構體和回調上也要進行手工編碼處理,不過比較簡單。
Cython簡單實例
我們嘗試用Cython,讓Python腳本調用C語言寫的打印“Hello World”的函數,來熟悉一下Cython的玩法。注:本文全部示例的完整代碼見gihub >>> cython_tutorials
/*filename: hello_world.h */ void print_hello_world();
/*filename: hello_world.c */ #include <stdio.h> #include "hello_world.h" void print_hello_world() { printf("hello world..."); } int main(int arch, char *argv[]) { print_hello_world(); return (0); }
#file: hello_world.pyx cdef extern from "hello_world.h": void print_hello_world() def cython_print_hello_world(): print_hello_world()
#filename: Makefile all: hello_world cython_hello_world hello_world: gcc hello_world.c -c hello_world.c gcc hello_world.o -o hello_world cython: cython cython_hello_world.pyx cython_hello_world: cython gcc cython_hello_world.c -fPIC -c gcc -shared -lpython2.7 -o cython_hello_world.so hello_world.o cython_hello_world.o clean: rm -rf hello_world hello_world.o cython_hello_world.so cython_hello_world.c cython_hello_world.o
用Cython擴展C,最重要的就是編寫.pyx腳本文件。.pyx腳本是Python調用C的橋梁,.pyx腳本中即能用Python語法寫,也可以用類C語法寫。
$ make all # 詳細的編譯過程可以看Makefile中的相關指令 $ python >>> import cython_hello_world >>> cython_hello_world.cython_print_hello_world() hello world... >>>
可以看到,我們成功的在Python解釋器中調用了C語言實現的函數。
Cython的注意事項
所有工具/語言的簡單使用都是令人愉快的,但是深入細節就會發現處處“暗藏殺機”。最近是項目需要擴展C底層庫給Python調用,所以引入了Cython。實踐過程中踩了很多坑,熬了很多夜T_T。遇到了以下幾點需要特別注意的點:
1. .pyx中用cdef定義的類型,除類以外對.py都不可見
我們來看一個例子:
#file: invisible.pyx cdef inline cdef_function(): print('cdef_function') def def_function(): print('def_function') cdef int cdef_value def_value = 999 cdef class cdef_class: def __init__(self): self.value = 1 class def_class: def __init__(self): self.value = 1
#file: test_visible.py import invisible if __name__ == '__main__': print('invisible.__dict__', invisible.__dict__)
輸出的invisible模塊的成員如下:
$ python invisible.py { '__builtins__': <module '__builtin__' (built-in)>, 'def_class': <class invisible.def_class at 0x10feed1f0>, '__file__': '/git/EasonCodeShare/cython_tutorials/invisible-for-py/invisible.so', 'call_all_in_pyx': <built-in function call_all_in_pyx>, '__pyx_unpickle_cdef_class': <built-in function __pyx_unpickle_cdef_class>, '__package__': None, '__test__': {}, 'cdef_class': <type 'invisible.cdef_class'>, '__name__': 'invisible', 'def_value': 999, 'def_function': <built-in function def_function>, '__doc__': None}
我們在.pyx用cdef定義的函數cdef_function、變量cdef_value都看不到了,只有類cdef_class能可見。所以,使用過程中要注意可見性問題,不要錯誤的在.py中嘗試使用不可見的模塊成員。
2. .py傳遞C結構體類型
Cython擴展C的能力僅限于.pyx腳本中,.py腳本還是只能用純Python。如果你在C中定義了一個結構,要從Python腳本中傳進來就只能在.pyx手工轉換一次,或者用包裹類傳進來。我們來看一個例子:
/*file: person_info.h */ typedef struct person_info_t { int age; char *gender; }person_info; void print_person_info(char *name, person_info *info);
//file: person_info.c #include <stdio.h> #include "person_info.h" void print_person_info(char *name, person_info *info) { printf("name: %s, age: %d, gender: %s\n", name, info->age, info->gender); }
#file: cython_person_info.pyx cdef extern from "person_info.h": struct person_info_t: int age char *gender ctypedef person_info_t person_info void print_person_info(char *name, person_info *info) def cyprint_person_info(name, info): cdef person_info pinfo pinfo.age = info.age pinfo.gender = info.gender print_person_info(name, &pinfo)
因為“cyprint_person_info”的參數只能是python object,所以我們要在函數中手工編碼轉換一下類型再調用C函數。
#file: test_person_info.py from cython_person_info import cyprint_person_info class person_info(object): age = None gender = None if __name__ == '__main__': info = person_info() info.age = 18 info.gender = 'male' cyprint_person_info('handsome', info)
$ python test_person_info.py name: handsome, age: 18, gender: male
能正常調用到C函數。可是,這樣存在一個問題,如果我們C的結構體字段很多,我們每次從.py腳本調用C函數都要手工編碼轉換一次類型數據就會很麻煩。還有更好的一個辦法就是給C的結構體提供一個包裹類。
#file: cython_person_info.pyx from libc.stdlib cimport malloc, free cdef extern from "person_info.h": struct person_info_t: int age char *gender ctypedef person_info_t person_info void print_person_info(char *name, person_info *info) def cyprint_person_info(name, person_info_wrap info): print_person_info(name, info.ptr) cdef class person_info_wrap(object): cdef person_info *ptr def __init__(self): self.ptr = <person_info *>malloc(sizeof(person_info)) def __del__(self): free(self.ptr) @property def age(self): return self.ptr.age @age.setter def age(self, value): self.ptr.age = value @property def gender(self): return self.ptr.gender @gender.setter def gender(self, value): self.ptr.gender = value
我們定義了一個“person_info”結構體的包裹類“person_info_wrap”,并提供了成員set/get方法,這樣就可以在.py中直接賦值了。減少了在.pyx中轉換數據類型的步驟,能有效的提高性能。
#file: test_person_info.py from cython_person_info import cyprint_person_info, person_info_wrap if __name__ == '__main__': info_wrap = person_info_wrap() info_wrap.age = 88 info_wrap.gender = 'mmmale' cyprint_person_info('hhhandsome', info_wrap)
$ python test_person_info.py name: hhhandsome, age: 88, gender: mmmale
3. python的str傳遞給C固定長度字符串要用strcpy
正如在C語言中,字符串之間不能直接賦值拷貝,而要使用strcpy復制一樣,python的str和C字符串之間也要用cython封裝的libc.string.strcpy函數來拷貝。我們稍微修改上一個例子,讓person_info結構體的gender成員為16字節長的字符串:
/*file: person_info.h */ typedef struct person_info_t { int age; char gender[16]; }person_info;
#file: cython_person_info.pyx cdef extern from "person_info.h": struct person_info_t: int age char gender[16] ctypedef person_info_t person_info
#file: test_person_info.py from cython_person_info import cyprint_person_info, person_info_wrap if __name__ == '__main__': info_wrap = person_info_wrap() info_wrap.age = 88 info_wrap.gender = 'mmmale' cyprint_person_info('hhhandsome', info_wrap)
$ make $ python test_person_info.py Traceback (most recent call last): File "test_person_info.py", line 7, in <module> info_wrap.gender = 'mmmale' File "cython_person_info.pyx", line 39, in cython_person_info.person_info_wrap.gender.__set__ self.ptr.gender = value File "stringsource", line 93, in carray.from_py.__Pyx_carray_from_py_char IndexError: not enough values found during array assignment, expected 16, got 6
cython轉換和make時候是沒有報錯的,運行的時候提示“IndexError: not enough values found during array assignment, expected 16, got 6”,其實就是6字節長的“mmmale”賦值給了person_info結構體的“char gender[16]”成員。我們用strcpy來實現字符串之間的拷貝就ok了。
#file: cython_person_info.pyx from libc.string cimport strcpy …… …… cdef class person_info_wrap(object): cdef person_info *ptr …… …… @property def gender(self): return self.ptr.gender @gender.setter def gender(self, value): strcpy(self.ptr.gender, value)
$ make $ python test_person_info.py name: hhhandsome, age: 88, gender: mmmale
賦值拷貝正常,成功將“mmmale”拷貝給了結構體的gender成員。
4. 用回調函數作為參數的C函數封裝
C中的回調函數比較特殊,用戶傳入回調函數來定制化的處理數據。Cython官方提供了封裝帶有回調函數參數的例子:
//file: cheesefinder.h typedef void (*cheesefunc)(char *name, void *user_data); void find_cheeses(cheesefunc user_func, void *user_data);
//file: cheesefinder.c #include "cheesefinder.h" static char *cheeses[] = { "cheddar", "camembert", "that runny one", 0 }; void find_cheeses(cheesefunc user_func, void *user_data) { char **p = cheeses; while (*p) { user_func(*p, user_data); ++p; } }
#file: cheese.pyx cdef extern from "cheesefinder.h": ctypedef void (*cheesefunc)(char *name, void *user_data) void find_cheeses(cheesefunc user_func, void *user_data) def find(f): find_cheeses(callback, <void*>f) cdef void callback(char *name, void *f): (<object>f)(name.decode('utf-8'))
import cheese def report_cheese(name): print("Found cheese: " + name) cheese.find(report_cheese)
關鍵的步驟就是在.pyx中定義一個和C的回調函數相同的回調包裹函數,如上的“cdef void callback(char *name, void *f)”。之后,將.py中的函數作為參數傳遞給包裹函數,并在包裹函數中轉換成函數對象進行調用。
擴展閱讀
更進一步的研究Cython可以參考官方文檔和相關書籍:
Cython 0.28a0 documentation
Cython A Guide for Python Programmers
Learning Cython Programming
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。