欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2019年08月01日 09:58:40   作者:葉雨珍  
這篇文章主要介紹了用Cython加速Python到“起飛”,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

事先聲明,標(biāo)題沒有把“Python”錯(cuò)打成“Cython”,因?yàn)橐v的就是名為“Cython”的東西。

Cython是讓Python腳本支持C語言擴(kuò)展的編譯器,Cython能夠?qū)ython+C混合編碼的.pyx腳本轉(zhuǎn)換為C代碼,主要用于優(yōu)化Python腳本性能或Python調(diào)用C函數(shù)庫。由于Python固有的性能差的問題,用C擴(kuò)展Python成為提高Python性能常用方法,Cython算是較為常見的一種擴(kuò)展方式。

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

有試用版水印,是因?yàn)楦FT_T

ctypes是Python標(biāo)準(zhǔn)庫支持的方案,直接在Python腳本中導(dǎo)入C的.so庫進(jìn)行調(diào)用,簡單直接。swig是一個(gè)通用的讓高級腳本語言擴(kuò)展支持C的工具,自然也是支持Python的。ctypes沒玩過,不做評價(jià)。以c語言程序性能為基準(zhǔn)的話,cython封裝后下降20%,swig封裝后下降70%。功能方面,swig對結(jié)構(gòu)體和回調(diào)函數(shù)都要使用typemap進(jìn)行手工編寫轉(zhuǎn)換規(guī)則,typemap規(guī)則寫起來略復(fù)雜,體驗(yàn)不是很好。cython在結(jié)構(gòu)體和回調(diào)上也要進(jìn)行手工編碼處理,不過比較簡單。

Cython簡單實(shí)例

我們嘗試用Cython,讓Python腳本調(diào)用C語言寫的打印“Hello World”的函數(shù),來熟悉一下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擴(kuò)展C,最重要的就是編寫.pyx腳本文件。.pyx腳本是Python調(diào)用C的橋梁,.pyx腳本中即能用Python語法寫,也可以用類C語法寫。

$ make all # 詳細(xì)的編譯過程可以看Makefile中的相關(guān)指令
$ python
>>> import cython_hello_world
>>> cython_hello_world.cython_print_hello_world()
hello world...
>>>

可以看到,我們成功的在Python解釋器中調(diào)用了C語言實(shí)現(xiàn)的函數(shù)。

Cython的注意事項(xiàng)

所有工具/語言的簡單使用都是令人愉快的,但是深入細(xì)節(jié)就會發(fā)現(xiàn)處處“暗藏殺機(jī)”。最近是項(xiàng)目需要擴(kuò)展C底層庫給Python調(diào)用,所以引入了Cython。實(shí)踐過程中踩了很多坑,熬了很多夜T_T。遇到了以下幾點(diǎn)需要特別注意的點(diǎn):

  1. .pyx中用cdef定義的東西,除類以外對.py都是不可見的;
  2. .py中是不能操作C類型的,如果想在.py中操作C類型就要在.pyx中從python object轉(zhuǎn)成C類型或者用含有set/get方法的C類型包裹類;
  3. 雖然Cython能對Python的str和C的“char *”之間進(jìn)行自動類型轉(zhuǎn)換,但是對于“char a[n]”這種固定長度的字符串是無法自動轉(zhuǎn)換的。需要使用Cython的libc.string.strcpy進(jìn)行顯式拷貝;
  4. 回調(diào)函數(shù)需要用函數(shù)包裹,再通過C的“void *”強(qiáng)制轉(zhuǎn)換后才能傳入C函數(shù)。

1. .pyx中用cdef定義的類型,除類以外對.py都不可見

我們來看一個(gè)例子:

#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定義的函數(shù)cdef_function、變量cdef_value都看不到了,只有類cdef_class能可見。所以,使用過程中要注意可見性問題,不要錯(cuò)誤的在.py中嘗試使用不可見的模塊成員。

2. .py傳遞C結(jié)構(gòu)體類型

Cython擴(kuò)展C的能力僅限于.pyx腳本中,.py腳本還是只能用純Python。如果你在C中定義了一個(gè)結(jié)構(gòu),要從Python腳本中傳進(jìn)來就只能在.pyx手工轉(zhuǎn)換一次,或者用包裹類傳進(jìn)來。我們來看一個(gè)例子:

/*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)

因?yàn)椤癱yprint_person_info”的參數(shù)只能是python object,所以我們要在函數(shù)中手工編碼轉(zhuǎn)換一下類型再調(diào)用C函數(shù)。

#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

能正常調(diào)用到C函數(shù)。可是,這樣存在一個(gè)問題,如果我們C的結(jié)構(gòu)體字段很多,我們每次從.py腳本調(diào)用C函數(shù)都要手工編碼轉(zhuǎn)換一次類型數(shù)據(jù)就會很麻煩。還有更好的一個(gè)辦法就是給C的結(jié)構(gòu)體提供一個(gè)包裹類。

#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

我們定義了一個(gè)“person_info”結(jié)構(gòu)體的包裹類“person_info_wrap”,并提供了成員set/get方法,這樣就可以在.py中直接賦值了。減少了在.pyx中轉(zhuǎn)換數(shù)據(jù)類型的步驟,能有效的提高性能。

#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復(fù)制一樣,python的str和C字符串之間也要用cython封裝的libc.string.strcpy函數(shù)來拷貝。我們稍微修改上一個(gè)例子,讓person_info結(jié)構(gòu)體的gender成員為16字節(jié)長的字符串:

/*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轉(zhuǎn)換和make時(shí)候是沒有報(bào)錯(cuò)的,運(yùn)行的時(shí)候提示“IndexError: not enough values found during array assignment, expected 16, got 6”,其實(shí)就是6字節(jié)長的“mmmale”賦值給了person_info結(jié)構(gòu)體的“char gender[16]”成員。我們用strcpy來實(shí)現(xiàn)字符串之間的拷貝就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”拷貝給了結(jié)構(gòu)體的gender成員。

4. 用回調(diào)函數(shù)作為參數(shù)的C函數(shù)封裝

C中的回調(diào)函數(shù)比較特殊,用戶傳入回調(diào)函數(shù)來定制化的處理數(shù)據(jù)。Cython官方提供了封裝帶有回調(diào)函數(shù)參數(shù)的例子

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

關(guān)鍵的步驟就是在.pyx中定義一個(gè)和C的回調(diào)函數(shù)相同的回調(diào)包裹函數(shù),如上的“cdef void callback(char *name, void *f)”。之后,將.py中的函數(shù)作為參數(shù)傳遞給包裹函數(shù),并在包裹函數(shù)中轉(zhuǎn)換成函數(shù)對象進(jìn)行調(diào)用。

擴(kuò)展閱讀

更進(jìn)一步的研究Cython可以參考官方文檔和相關(guān)書籍:

Cython 0.28a0 documentation

Cython A Guide for Python Programmers

Learning Cython Programming

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • python項(xiàng)目--使用Tkinter的日歷GUI應(yīng)用程序

    python項(xiàng)目--使用Tkinter的日歷GUI應(yīng)用程序

    在 Python 中,我們可以使用 Tkinter 制作 GUI。如果你非常有想象力和創(chuàng)造力,你可以用 Tkinter 做出很多有趣的東西,希望本篇文章能夠幫到你
    2021-08-08
  • Python中urllib+urllib2+cookielib模塊編寫爬蟲實(shí)戰(zhàn)

    Python中urllib+urllib2+cookielib模塊編寫爬蟲實(shí)戰(zhàn)

    這篇文章主要介紹了Python的urllib+urllib2+cookielib模塊編寫爬蟲實(shí)戰(zhàn),文中給出了抓取豆瓣同城和登陸圖書館查詢圖書歸還的爬取例子,需要的朋友可以參考下
    2016-01-01
  • Python實(shí)現(xiàn)將內(nèi)容轉(zhuǎn)為base64編碼與解碼

    Python實(shí)現(xiàn)將內(nèi)容轉(zhuǎn)為base64編碼與解碼

    這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)將內(nèi)容轉(zhuǎn)為base64編碼與解碼的示例代碼,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-02-02
  • Python實(shí)現(xiàn)DHCP請求方式

    Python實(shí)現(xiàn)DHCP請求方式

    這篇文章主要介紹了Python實(shí)現(xiàn)DHCP請求方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • Python3 shutil(高級文件操作模塊)實(shí)例用法總結(jié)

    Python3 shutil(高級文件操作模塊)實(shí)例用法總結(jié)

    在本篇文章里小編給大家整理的是一篇關(guān)于Python3 shutil實(shí)例用法內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。
    2020-02-02
  • Python?超簡潔且詳細(xì)爬取西瓜視頻案例

    Python?超簡潔且詳細(xì)爬取西瓜視頻案例

    今天給大家?guī)硪黄廊∥鞴弦曨l的小教程,很簡單的幾十行代碼就可以實(shí)現(xiàn)了,每一段代碼都詳細(xì)說明了用途,看完你就能夠自己動手寫,感興趣的同學(xué)快來跟著小編往下看吧
    2021-11-11
  • Python圖像增強(qiáng)imgaug詳解

    Python圖像增強(qiáng)imgaug詳解

    Imgaug 是一個(gè)開源 python 包,可讓你在機(jī)器學(xué)習(xí)實(shí)驗(yàn)中增強(qiáng)圖像,它適用于各種增強(qiáng)技術(shù),它有一個(gè)簡單而強(qiáng)大的界面,可以增強(qiáng)圖像、地標(biāo)、邊界框、熱圖和分割圖,這篇文章主要介紹了Python圖像增強(qiáng)imgaug,需要的朋友可以參考下
    2022-11-11
  • wxPython實(shí)現(xiàn)畫圖板

    wxPython實(shí)現(xiàn)畫圖板

    這篇文章主要為大家詳細(xì)介紹了wxPython實(shí)現(xiàn)畫圖板,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • 詳解Django中間件執(zhí)行順序

    詳解Django中間件執(zhí)行順序

    這篇文章主要介紹了詳解Django中間件執(zhí)行順序,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-07-07
  • python簡單文本處理的方法

    python簡單文本處理的方法

    這篇文章主要介紹了python簡單文本處理的方法,涉及Python針對文本文件及字符串操作的相關(guān)技巧,需要的朋友可以參考下
    2015-07-07

最新評論