本文主要介绍如何使用Cython加速Python的“腾飞”。通过示例代码进行了非常详细的介绍,对于大家的学习或者工作都有一定的参考价值。有需要的朋友就跟着下面的边肖学习吧。
声明一下,标题没有把“Python”错印成“Cython”,因为它是一个叫做“Cython”的东西。
Cython是一个允许Python脚本支持C语言扩展的编译器。Cython可以转换Python C代码。pyx脚本转换成C代码,主要用于优化Python脚本性能或者Python调用C函数库。由于Python固有的性能差,用C扩展Python成为了提高Python性能的常用方法,Cython就是一种常用的扩展方法。
我们可以对比一下业界几个主流的支持C语言的Python扩展:
因为T _ T差所以有水印的试用版。
Ctypes是Python标准库支持的一种方案。直接导入C的简单直接。所以Python脚本中的库用于调用。Swig是扩展高级脚本语言支持C的通用工具,自然也支持Python。Ctypes没玩过,不做评价。基于C语言程序的性能,cython封装后下降20%,swig封装后下降70%。在函数方面,swig使用typemap手工编写结构和回调函数的转换规则。typemap规则写起来略复杂,体验不是很好。Cython也必须在结构和回调上手工编码,但相对简单。
Cython简单示例
我们试着用Cython,让Python脚本调用打印C语言写的“Hello World”的函数,熟悉一下Cython的玩法。注意:请参阅gihub cython_tutorials,获取本文中所有示例的完整代码。
/*文件名:hello_world.h */
void print _ hello _ world();
/*文件名:hello_world.c */
#包含stdio.h
#包含“hello_world.h”
void print_hello_world()
{
printf('你好,世界…');
}
int main(int arch,char *argv[])
{
print _ hello _ world();
return(0);
}
#file: hello_world.pyx
“hello_world.h”中的cdef extern:
void print_hello_world()
def cython_print_hello_world():
print_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-lpython 2.7-o cy thon _ hello _ world . so hello _ world . o cy thon _ hello _ world . o
清洁:
RM-RF hello _ world hello _ world . o cy thon _ hello _ world . so cy thon _ hello _ world . c cy thon _ hello _ world . o
用Cython扩展C最重要的是写。pyx脚本文件。的。pyx脚本是Python调用c的桥梁。pyx脚本可以用Python语法或类似C的语法编写。
$ make all #详细编译过程参见Makefile中的相关说明。
$ python
导入cython_hello_world
cy thon _ hello _ world . cy thon _ print _ hello _ world()
你好世界.
可以看到,我们在Python解释器中成功调用了C语言实现的函数。
Cython的注意事项
所有工具/语言的简单使用都令人愉悦,但如果深入细节,你会发现“隐患”无处不在。最近项目需要将C-base库扩展到Python,于是引入了Cython。实践中踩了很多坑,熬了很多夜。遇到以下几点需要特别注意:
中用cdef定义的所有内容。pyx对是不可见的。py除了课;
c类型不能在中操作。py。如果你想操作C类型的。py,你应该在中从python对象改成C类型。pyx或者用包含set/get方法的C类型包装类。
虽然Cython可以在Python的str和C的“char *”之间进行自动类型转换,但是它不能自动转换定长字符串“char a[n]”。使用libc字符串;Cython的strcpy用于显式复制;
回调函数需要包装在一个函数中,然后由C的“void *”进行强制转换,才能传入C函数。
1. .pyx中用cdef定义的类型,除类以外对.py都不可见
让我们看一个例子:
#file: invisible.pyx
cdef内联cdef_function():
打印(' cdef_function ')
def def_function():
打印(“def_function”)
cdef int cdef _值
定义值=999
cdef类cdef_class:
def __init__(self):
self.value=1
类别定义_类别:
def __init__(self):
self.value=1
#file: test_visible.py
导入不可见
if __name__=='__main__ ':
打印('隐形__dict__ ',不可见。__字典_ _)
输出的看不见的模块的成员如下:
$ python invisible.py
{
__builtin__ ':模块_ _ builtin _ _ '(内置),
' def _ class ':0x 10馈送1 f 0处的class invisible.def_class,
_ _ file _ _ ':'/git/EasonCodeShare/cy thon _ tutorials/invisible-for-py/invisible。所以,
call_all_in_pyx ':内置函数call_all_in_pyx,
__pyx_unpickle_cdef_class ':内置函数__pyx_unpickle_cdef_class,
__包_ _ ':无,
__test__': {},
" cdef_class ":类型" invisible.cdef_class ",
__name__ ':'不可见',
def_value': 999,
def_function ':内置函数def _函数,
__doc__ ':无}
我们在.圣体容器用cdef定义的函数cdef _函数变量cdef _值都看不到了,只有类cdef_class能可见。所以,使用过程中要注意可见性问题,不要错误的在.巴拉圭中尝试使用不可见的模块成员。
2. .py传递C结构体类型
Cython扩展英语字母表中第三个字母的能力仅限于.圣体容器脚本中,巴拉圭脚本还是只能用纯Python。如果你在英语字母表中第三个字母中定义了一个结构,要从大蟒脚本中传进来就只能在.圣体容器手工转换一次,或者用包裹类传进来。我们来看一个例子:
/*file: person_info.h */
typedef结构个人信息
{
年龄
字符*性别;
}个人信息
void print _ person _ info(char * name,person _ info * info);
//file: person_info.c
#包含标准视频
#include 'person_info.h '
void print _ person _ info(char * name,person_info *info)
{
printf('姓名:%s,年龄:%d,性别:%s\n ',
姓名、信息年龄、信息性别);
}
#file: cython_person_info.pyx
' person_info.h '中的cdef外部:
结构个人信息:
年龄
字符*性别
ctypedef个人信息
void print _ person _ info(char * name,person_info *info)
def cyprint_person_info(姓名,信息):
cdef人员信息品福
pinfo.age=info.age
pinfo.gender=info.gender
打印个人信息(姓名,个人信息)
因为"塞浦路斯人信息"的参数只能是大蟒对象,所以我们要在函数中手工编码转换一下类型再调用英语字母表中第三个字母函数。
#file: test_person_info.py
从cython_person_info导入塞浦路斯人信息
类人员信息(对象):
年龄=无
性别=无
if __name__=='__main__ ':
info=person_info()
信息年龄=18岁
info.gender='男性'
塞浦路斯人信息('帅,信息)
$ python test_person_info.py
姓名:帅,年龄:18,性别:男
能正常调用到英语字母表中第三个字母函数。可是,这样存在一个问题,如果我们英语字母表中第三个字母的结构体字段很多,我们每次从.巴拉圭脚本调用英语字母表中第三个字母函数都要手工编码转换一次类型数据就会很麻烦。还有更好的一个办法就是给英语字母表中第三个字母的结构体提供一个包裹类。
#file: cython_person_info.pyx
来自libc.stdlib cimport malloc,免费
' person_info.h '中的cdef外部:
结构个人信息:
年龄
字符*性别
ctypedef个人信息
void print _ person _ info(char * name,person_info *info)
def cyprint_person_info(姓名,人员信息_包装信息):
打印个人信息(姓名,信息.ptr)
cdef类person_info_wrap(对象):
cdef人员信息*ptr
def __init__(self):
自我。ptr=person _ info * malloc(sizeof(person _ info))
def __del__(self):
免费(self.ptr)
@属性
定义年龄(自己):
回归自我
@age.setter
定义年龄(自我,价值):
self.ptr.age=value
@属性
定义性别(自我):
返回self.ptr .性别
@gender.setter
定义性别(自我,价值):
self.ptr.gender=value
我们定义了一个"个人信息"结构体的包裹类"个人信息包装",并提供了成员设置/获取方法,这样就可以在.巴拉圭中直接赋值了。减少了在.圣体容器中转换数据类型的步骤,能有效的提高性能。
#file: test_person_info.py
从cython_person_info导入cyprint_person_info,person_info_wrap
if __name__=='__main__ ':
info_wrap=person_info_wrap()
info_wrap.age=88
info_wrap.gender='mmmale '
cy print _ person _ info(' hh shame ',info_wrap)
$ python test_person_info.py
姓名:hh帅哥,年龄:88岁,性别:男
3. python的str传递给C固定长度字符串要用strcpy
正如在C语言中,字符串之间不能直接赋值拷贝,而要使用strcpy复制一样,python的潜艇用热中子反应堆(海底热反应堆的缩写)和C字符串之间也要用cython封装的libc。字符串函数来拷贝。我们稍微修改上一个例子,让个人信息结构体的性别成员为16字节长的字符串:
/*file: person_info.h */
typedef结构个人信息
{
年龄
茶性别[16];
}个人信息
#file: cython_person_info.pyx
' person_info.h '中的cdef外部:
结构个人信息:
年龄
茶性别[16]
ctypedef个人信息
#file: test_person_info.py
从cython_person_info导入cyprint_person_info,person_info_wrap
if __name__=='__main__ ':
info_wrap=person_info_wrap()
info_wrap.age=88
info_wrap.gender='mmmale '
cy print _ person _ info(' hh shame ',info_wrap)
$ make
$ python test_person_info.py
回溯(最近一次呼叫):
模块中文件" test_person_info.py "的第七行
info_wrap.gender='mmmale '
文件“cy thon _ person _ info。个人信息包装。性别。_ _ set __中的第39行
self.ptr.gender=value
carray.from_py中文件"字符串源"的第93行. Pyx_carray_from_py_char
索引错误:数组赋值期间找到的值不足,应为16个,实际为6个
cython转换和制造时候是没有报错的,运行的时候提示索引错误:数组赋值期间找到的值不足,应为16个,但得到了6个,其实就是6字节长的“mmmale”赋值给了个人信息结构体的"字符性别[16]"成员。我们用strcpy来实现字符串之间的拷贝就好的了。
#file: cython_person_info.pyx
from libc.string cimport strcpy
…… ……
cdef类person_info_wrap(对象):
cdef人员信息*ptr
…… ……
@属性
定义性别(自我):
返回self.ptr .性别
@gender.setter
定义性别(自我,价值):
strcpy(self.ptr.gender,value)
$ make
$ python test_person_info.py
姓名:hh帅哥,年龄:88岁,性别:男
赋值拷贝正常,成功将“mmmale”拷贝给了结构体的性别成员。
4.用回调函数作为参数的C函数封装
C中的回调函数比较特殊,用户传入回调函数来定制化的处理数据Cython官方提供了封装带有回调函数参数的例子:
//file: cheesefinder.h
typedef void(* cheese func)(char * name,void * user _ data);
void find _ cheeses(cheese func user _ func,void * user _ data);
//file: cheesefinder.c
#包含"奶酪发现者. h "
静态char *cheeses[]={
切达干酪,
卡门贝干酪,
那个流鼻涕的,
0
};
void find _ cheeses(cheese func user _ func,void *user_data) {
char **p=奶酪;
while (*p) {
user_func(*p,user _ data);
p;
}
}
#file: cheese.pyx
"奶酪发现者. h "中的cdef外部:
ctypedef void(* cheese func)(char * name,void *user_data)
void find _ chees(cheese func user _ func,void *user_data)
定义查找(f):
寻找奶酪(回调,void*f)
cdef无效回调(char *name,void *f):
(objectf)(name.decode('utf-8 '))
进口奶酪
定义报告_奶酪(名称):
打印('找到的奶酪: '名称)
芝士. find(报表_奶酪)
关键的步骤就是在。圣体容器中定义一个和C的回调函数相同的回调包裹函数,如上的cdef无效回调(char *name,void *f)。之后,将。巴拉圭中的函数作为参数传递给包裹函数,并在包裹函数中转换成函数对象进行调用。
扩展阅读
更进一步的研究Cython可以参考官方文档和相关书籍:
Cython 0.28a0文档
计算机编程语言程序员指南
学习Cython编程
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。