Python与CC++交互的⼏种⽅式
python作为⼀门脚本语⾔,其好处是语法简单,很多东西都已经封装好了,直接拿过来⽤就⾏,所以实现同样⼀个功能,⽤Python写要⽐⽤C/C++代码量会少得多。但是优点也必然也伴随着缺点(这是肯定的,不然还要其他语⾔⼲嘛),python最被⼈诟病的⼀个地⽅可能就是其运⾏速度了。这这是⼤部分脚本语⾔共同⾯对的问题,因为没有编译过程,直接逐⾏执⾏,所以要慢了⼀⼤截。所以在⼀些对速度要求很⾼的场合,⼀般都是使⽤C/C++这种编译型语⾔来写。但是很多时候,我们既想使⽤python的简介优美,⼜不想损失太多的性能,这个时候有没有办法将python与C/C++结合到⼀起呢?这样在性能与速度要求不⾼的地⽅,可以⽤pyhton写,⽽关键的运算部分⽤C/C++写,这样就太好了。python在做科学计算或者数据分析时,这是⼀个⾮常普遍的需求。要想实现这个功能,python为我们提供了不⽌⼀种解决办法。下⾯我就逐⼀给⼤家介绍。
⼀、Cython 混合python与C
官⽅⽹址:/en/latest/src/quickstart/overview.html。⾸先来看看cython的官⽅介绍吧。
[Cython] is a programming language that makes writing C extensions for the Python language as easy as Python itself. It aims to become a superset of the language which gives it high-level, object-oriented,
functional, and dynamic programming. Its main feature on top of these is support for optional static type declarations as part of the language. The source code gets translated into optimized C/C++ code and compiled as Python extension modules. This allows for both very fast program execution and tight integration with external C libraries, while keeping up the high programmer productivity for which the Python language is well known.
简单来说,cython就是⼀个内置了c数据类型的python,它是⼀个python的超集,兼容⼏乎所有的纯python代码,但是⼜可以使⽤c的数据类型。这样就可以同时使⽤c库,⼜不失python的优雅。
好了,不讲太多废话,直接来看cython如何使⽤吧。这⾥的介绍⼤部分来⾃官⽹,由于cython涉及到的东西还⽐较多,所以这⾥只是简单的⼊门介绍,详细的信息请移步英⽂官⽹。
使⽤cython有两种⽅式:第⼀个是编译⽣成Python扩展⽂件(有点类似于dll,即动态链接库),可以直接import使⽤。第⼆个是使⽤jupyter notebook或sage notebook 内联 cython代码。
先看第⼀种。还是举最经典的hello world的例⼦吧。新建⼀个hello.pyx⽂件,定义⼀个hello函数如下:
def hello(name):
print("Hello %s." % name)
然后,我们来写⼀个setup.py ⽂件(写python扩展⼏乎都要写setup.py⽂件,我之前也简单介绍过怎么写)如下:
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# @Time : 2017/5/8 9:09
4# @Author : Lyrichu
5# @Email : 919987476@qq
6# @File : setup.py
7'''
8@Description: setup.py for hello.pyx
9'''
10from Cython.Build import cythonize
import setup
12
13# 编写setup函数
14 setup(
15 name = "Hello",
16 ext_modules = cythonize("hello.pyx")
17 )
其中 ext_modules ⾥⾯写你要编译的.pyx⽂件名字。OK,所有⼯作都完成了。接下来,进⼊cmd,切换到setup.py 所在的⽂件,然后执⾏命令: python setup.py build_ext --inplace 就会编译⽣成⼀个build ⽂件夹以及⼀个.pyd⽂件了,这个pyd⽂件就是python的动态扩展库,--inplace 的意思是在当前⽂件⽬录下⽣成.pyd⽂件,不加这⼀句就会在build⽂件夹中⽣成。截图如下:
图 1
可以看出,除了⽣成了⼀个pyd⽂件之外,还⽣成了⼀个.c⽂件。test.py是我们⽤来测试的⽂件,在⾥⾯写如下内容:
from hello import hello
hello("lyric")
从hello 模块导⼊ hello函数,然后直接调⽤就可以了。结果输出 Hello lyric.
再来看如何在 jupyter notebook中使⽤cython。如果你装过ipython,⼀个升级版的python交互式环境,你应该听过 ipyhton notebook的⼤名,现在它升级了,改名叫jupyter notebook 了。简单来说,这个就是⼀个可以在⽹页环境下交互式使⽤python的⼯具,不仅可以实时看到计算结果,还可以直接展
⽰表格,图⽚等,功能还是⾮常强⼤的。⾸先你得安装jupyter notebook.我印象中安装了ipython之后应该就会带了jupyter了。如果没有,可以直接 pip install jupyter .然后输⼊命令 jupyter notebook 就会在浏览器中打开jupyter了。如下图2 所⽰:
图 2
点击右上⾓的new按钮,可以选择新建⼀个⽂本⽂件或者⽂件夹,markdown或者python⽂件,这⾥我们选择新建⼀个pyhton ⽂件,然后就会转到⼀个新的窗⼝了,如下图3:
图 3
In[]:和ipython⼀样,就代表着我们要输⼊代码的地⽅,输⼊代码之后,点击向右的三⾓形符号,就会执⾏代码了。
⾸先输⼊ %load_ext cython ,然后执⾏,%开头的语句是jupyter的魔法命令,%是⾏命令,%%是单元命令,具体不多说,有空给⼤家专门介绍⼀下notebook的使⽤。
接下来输⼊:
1 %%cython
2 cdef int a = 0
3for i in range(10):
4 a += i
5print(a)
%%cython 表明将cython内嵌到jupyter,cdef 是cython的关键字,⽤于定义c类型,这⾥将a定义为c中的int类型,并且初始化为0.
然后后⾯的循环就是累加0到9的意思,最后输出45.
另外,我们如果想分析代码的执⾏情况,可以输⼊ %%cython --annotate 命令,这样就可以输出结果的同时,也输出详细的代码执⾏情况报告了。截图如图4 所⽰:
图 4
jupyter notebook 可以内嵌cython,不⽤我们⼿写setup.py ⽂件,省去了编译的过程,⽅便了cython的使⽤,所以不是正式做项⽬,只是写⼀写⼩东西⽤jupyter+cython还是⾮常⽅便的。
前⾯提到了 cdef,再举⼀个稍微复杂点的例⼦吧。还是引⽤官⽹的例⼦,写⼀个算积分的函数.新建 integrate.pyx ⽂件,写⼊如下内容:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2017/5/8 9:26
# @Author : Lyrichu编程先学c语言还是python
# @Email : 919987476@qq
# @File : integrate.py
'''
@Description: 积分运算,使⽤ cython cdef 关键字
'''
def f(double x):
return x**2 - x
def integrate_f(double a,double b,int N):
cdef int i
cdef double s,dx
s = 0
dx = (b-a)/N
for i in range(N):
s += f(a + i*dx)*dx
return s # 返回定积分
这段代码应该也是⽐较好理解的,f()函数是被积函数,a,b是积分的上下限,N是分割⼩矩形的个数,注意这⾥将变量i,s,dx全部都⽤cdef 声明为c类型了,⼀般来说,在需要密集计算的地⽅⽐如循环或者复杂运算,可以将对应的变量声明为c类型,可以加快运⾏速度。
然后和上⾯⼀样编写 setup.py ,就是把 pyx的⽂件名改⼀下,代码我就不贴了。然后python setup.py build_ext --inplace 执⾏。得到pyd⽂件,编写测试⽂件test.py如下:
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# @Time : 2017/5/8 9:35
4# @Author : Lyrichu
5# @Email : 919987476@qq
6# @File : test.py
7'''
8@Description: 测试使⽤cython 混合c与python的integrate 函数与纯python写的integrate函数速度上的差异
9'''
10from integrate import integrate_f
11import time
12
13 a = 1 # 积分区间下界
14 b = 2 # 积分区间上界
15 N = 10000 # 划分区间个数
16
17# 使⽤纯python代码写的integrate函数
18def py_f(x):
19return x**2 - x
20
21def py_integrate_f(a,b,N):
22 dx = (b-a)/N
23 s = 0
24for i in range(N):
25 s += py_f(a + i*dx)*dx
26return s
27
28 start_time1 = time.time()
29 integrate_f_res = integrate_f(a,b,N)
30print("integrate_f_res = %s" % integrate_f_res)
31 end_time1 = time.time()
32print(u"cython 版本计算耗时:%.8f" % (end_time1 - start_time1))
33
34 start_time2 = time.time()
35 py_integrate_f_res = py_integrate_f(a,b,N)
36print("py_integrate_f_res = %s" % py_integrate_f_res)
37 end_time2 = time.time()
38print(u"python 版本计算耗时:%.8f" % (end_time2 - start_time2))
上⾯的代码,我们重新使⽤python写了⼀个积分函数py_integrate_f,与pyd中的integrate_f 函数进⾏运算对⽐,结果如下(图5):
图5
可以看出,使⽤了cython的版本⽐纯Python的版本⼤概快了4、5倍的样⼦,⽽这仅仅是将⼏个变量改
为c类型的结果,可见,cython确实可以⽅便地对python与c进⾏混合,获得速度上的提升,⼜不失去Python的简洁优美。
最后再来说下cython 如何调⽤c libraries. C 语⾔ stdlib 库有⼀个 atoi函数,可以将字符串转化为整数,math库有⼀个sin函数,我们就以这两个函数为例。新建 calling_c.pyx ⽂件,⽂件内容如下:
from libc.stdlib cimport atoi
from libc.math cimport sin
def parse_char_to_int(char * s):
assert s is not NULL,"byte string value is NULL"
return atoi(s)
def f_sin_squared(double x):
return sin(x*x)
前两⾏导⼊了C语⾔中的函数,然后我们⾃定义了两个函数,parse_char_to_int 可以将字符串转换为
整数,f_sin_squared 计算 x平⽅的sin 函数值。写 setup.py ⽂件,和之前差不多,但是要注意的是,在unix系统下,math库默认是不链接的,所以需要指明其位置,那么在unix系统下,setup.py ⽂件的内容就需要增加Extension ⼀项,如下:
import setup
sion import Extension
from Cython.Build import cythonize
ext_modules=[
Extension("calling_c",
sources=["calling_c.pyx"],
libraries=["m"] # Unix-like specific
)
]
setup(
name = "Calling_c",
ext_modules = cythonize(ext_modules)
)
然后直接编即可。test.py⽂件如下:
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# @Time : 2017/5/8 12:21
4# @Author : Lyrichu
5# @Email : 919987476@qq
6# @File : test.py
7'''
8@Description: test file
9'''
10from calling_c import f_sin_squared,parse_char_to_int
11 str = "012"
12 str_b = bytes(str,encoding='utf-8')
13 n = parse_char_to_int(str_b)
14print("n = %d" %n)
15from math import pi,sqrt
16 x = sqrt(pi/2)
17 res = f_sin_squared(x)
18print("sin(pi/2)=%f" % res)
需要注意的是,Python字符串不能直接传⼊ parse_char_to_int 函数,需要将其转换为 bytes 类型再传⼊。运⾏结果为:
n = 12
sin(pi/2)=1.000000
如果不想通过libc导⼊c语⾔模块,cython也允许我们⾃⼰声明c函数原型来导⼊,⼀个例⼦如下:
# ⾃⼰声明c函数原型
cdef extern from"math.h":
cpdef double cos(double x)
def f_cos(double x):
return cos(x)
使⽤了 extern 关键字。
每次都编写setup.py ⽂件,然后编译,略显⿇烦。cython还提供了⼀种更简单的⽅法:pyximport。通过导⼊pyximport(安装cython时会⾃动安装),在没有引⼊额外的c库的情况下,可以直接调⽤pyx中的函数,更为直接与⽅便。以前⾯的hello 模块为例,编写好hello.py⽂件之后,编写⼀个pyximport_test.py ⽂件,⽂件内容如下:
import pyximport
pyximport.install()
import hello
hello.hello("lyric")
直接运⾏就会发现,确实可以正确导⼊hello模块。
cython的更多内容,请⼤家⾃⾏访问官⽹查看。
其他python与c/c++ 混合编程的⽅式主要还有使⽤ ctypes,cffi模块以及swig。本来想⼀起写的,想想还是分开写吧,不然太长了。后续会陆续更新,敬请关注。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论