Python 与 C/C++ 交互的几种方式

  python做为一门脚本语言,其好处是语法简单,不少东西都已经封装好了,直接拿过来用就行,因此实现一样一个功能,用Python写要比用C/C++代码量会少得多。可是优势也必然也伴随着缺点(这是确定的,否则还要其余语言干吗),python最被人诟病的一个地方可能就是其运行速度了。这这是大部分脚本语言共同面对的问题,由于没有编译过程,直接逐行执行,因此要慢了一大截。因此在一些对速度要求很高的场合,通常都是使用C/C++这种编译型语言来写。可是不少时候,咱们既想使用python的简介优美,又不想损失太多的性能,这个时候有没有办法将python与C/C++结合到一块儿呢?这样在性能与速度要求不高的地方,能够用pyhton写,而关键的运算部分用C/C++写,这样就太好了。python在作科学计算或者数据分析时,这是一个很是广泛的需求。要想实现这个功能,python为咱们提供了不止一种解决办法。下面我就逐一给你们介绍。html

  1、Cython 混合python与Cpython

  官方网址:http://docs.cython.org/en/latest/src/quickstart/overview.html。首先来看看cython的官方介绍吧。c++

[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 [Python]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涉及到的东西还比较多,因此这里只是简单的入门介绍,详细的信息请移步英文官网。markdown

使用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.com
 6 # @File   : setup.py
 7 '''
 8 @Description: setup.py for hello.pyx
 9 '''
10 from Cython.Build import cythonize
11 from distutils.core 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
3 for i in range(10):
4     a += i
5 print(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
# @Email  : 919987476@qq.com
# @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.com
 6 # @File   : test.py
 7 '''
 8 @Description: 测试使用cython 混合c与python的integrate 函数与纯python写的integrate函数速度上的差别
 9 '''
10 from integrate import integrate_f
11 import time
12 
13 a = 1 # 积分区间下界
14 b = 2 # 积分区间上界
15 N = 10000 # 划分区间个数
16 
17 # 使用纯python代码写的integrate函数
18 def py_f(x):
19     return x**2 - x
20 
21 def py_integrate_f(a,b,N):
22     dx = (b-a)/N
23     s = 0
24     for i in range(N):
25         s += py_f(a + i*dx)*dx
26     return s
27 
28 start_time1 = time.time()
29 integrate_f_res = integrate_f(a,b,N)
30 print("integrate_f_res = %s" % integrate_f_res)
31 end_time1 = time.time()
32 print(u"cython 版本计算耗时:%.8f" % (end_time1 - start_time1))
33 
34 start_time2 = time.time()
35 py_integrate_f_res = py_integrate_f(a,b,N)
36 print("py_integrate_f_res = %s" % py_integrate_f_res)
37 end_time2 = time.time()
38 print(u"python 版本计算耗时:%.8f" % (end_time2 - start_time2))

上面的代码,咱们从新使用python写了一个积分函数py_integrate_f,与pyd中的integrate_f 函数进行运算对比,结果以下(图5):

图5

能够看出,使用了cython的版本比纯Python的版本大概快了四、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 一项,以下:

from distutils.core import setup
from distutils.extension 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.com
 6 # @File   : test.py
 7 '''
 8 @Description: test file
 9 '''
10 from 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)
14 print("n = %d" %n)
15 from math import pi,sqrt
16 x = sqrt(pi/2)
17 res = f_sin_squared(x)
18 print("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。原本想一块儿写的,想一想仍是分开写吧,否则太长了。后续会陆续更新,敬请关注。

相关文章
相关标签/搜索