亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

用Cython加速Python到“起飛”(推薦)

系統 1803 0

事先聲明,標題沒有把“Python”錯打成“Cython”,因為要講的就是名為“Cython”的東西。

Cython是讓Python腳本支持C語言擴展的編譯器,Cython能夠將Python+C混合編碼的.pyx腳本轉換為C代碼,主要用于優化Python腳本性能或Python調用C函數庫。由于Python固有的性能差的問題,用C擴展Python成為提高Python性能常用方法,Cython算是較為常見的一種擴展方式。

我們可以對比一下業界主流的幾種Python擴展支持C語言的方案:

用Cython加速Python到“起飛”(推薦)_第1張圖片

有試用版水印,是因為窮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 
            
              
#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都是不可見的;
  2. .py中是不能操作C類型的,如果想在.py中操作C類型就要在.pyx中從python object轉成C類型或者用含有set/get方法的C類型包裹類;
  3. 雖然Cython能對Python的str和C的“char *”之間進行自動類型轉換,但是對于“char a[n]”這種固定長度的字符串是無法自動轉換的。需要使用Cython的libc.string.strcpy進行顯式拷貝;
  4. 回調函數需要用函數包裹,再通過C的“void *”強制轉換后才能傳入C函數。

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__': 
            
              , 
'def_class': 
              
                , 
'__file__': '/git/EasonCodeShare/cython_tutorials/invisible-for-py/invisible.so', 
'call_all_in_pyx': 
                
                  , 
'__pyx_unpickle_cdef_class': 
                  
                    , 
'__package__': None, 
'__test__': {}, 
'cdef_class': 
                    
                      , 
'__name__': 'invisible', 
'def_value': 999, 
'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 
            
              
#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 = 
            
              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 
            
              
  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, 
            
              f)

cdef void callback(char *name, void *f):
  (
              
                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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 羞羞视频免费观看网站 | 国产在线精品二区赵丽颖 | 热久久久| 中文字幕久久久久久久系列 | a毛片基地 | 99国产精品欧美久久久久久影院 | 国产女主播在线 | 色吧五月婷婷 | 久久精品国产欧美日韩99热 | 欧美一级毛片片aa视频 | 99久久精品99999久久 | 天天干天天玩天天操 | 日本大黄视频 | 激情国产白嫩美女在线观看 | 国产精品一区二区三区四区五区 | 亚洲综合日韩精品欧美综合区 | 国产专区在线播放 | 伊人免费| 精品国产成人三级在线观看 | 中文精品视频一区二区在线观看 | 久久精品亚洲欧美日韩久久 | 欧美乱操| 亚洲欧洲免费视频 | 国产波多野结衣中文在线播放 | 天天久久狠狠伊人第一麻豆 | 国产成在线人视频免费视频 | 亚洲欧美不卡 | 99精品视频在线免费观看 | 人人干天天干 | 人人澡人人澡人人看欧美 | 九月丁香婷婷亚洲综合色 | 一区二区三区四区在线视频 | 国产亚洲精品看片在线观看 | 亚洲视频一区二区 | 色婷婷亚洲十月十月色天 | www国产视频 | 五月天激情视频在线观看 | 日韩中文字幕免费观看 | 久久影院朴妮唛 | 一区二区三区精品国产 | 夜夜爽夜夜操 |