Python和C/C++交互的几种方法总结


Posted in Python onMay 11, 2017

前言

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

下面我就逐一给大家介绍。

一、Cython 混合python与C

官方网址:http://docs.cython.org/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 [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涉及到的东西还比较多,所以这里只是简单的入门介绍,详细的信息请移步英文官网。

使用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文件,我之前也简单介绍过怎么写)如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2017/5/8 9:09
# @Author : Lyrichu
# @Email : 919987476@qq.com
# @File : setup.py
'''
@Description: setup.py for hello.pyx
'''
from Cython.Build import cythonize
from distutils.core import setup

# 编写setup函数
setup(
 name = "Hello",
 ext_modules = cythonize("hello.pyx")
)

其中 ext_modules 里面写你要 编译的.pyx文件名字。OK,所有工作都完成了。接下来,进入cmd,切换到setup.py 所在的文件,然后执行命令: python setup.py build_ext --inplace 就会编译生成一个build 文件夹以及一个.pyd文件了,这个pyd文件就是python的动态扩展库,--inplace 的意思是在当前文件目录下生成.pyd文件,不加这一句就会在build文件夹中生成。

截图如下:

Python和C/C++交互的几种方法总结

图 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 所示:

Python和C/C++交互的几种方法总结

图 2

点击右上角的new按钮,可以选择新建一个文本文件或者文件夹,markdown或者python文件,这里我们选择新建一个pyhton 文件,然后就会转到一个新的窗口了,如下图3:

Python和C/C++交互的几种方法总结

图 3

In[]:和ipython一样,就代表着我们要输入代码的地方,输入代码之后,点击向右的三角形符号,就会执行代码了。

首先输入 %load_ext cython ,然后执行,%开头的语句是jupyter的魔法命令,%是行命令,%%是单元命令,具体不多说,有空给大家专门介绍一下notebook的使用。

接下来输入:

%%cython
 cdef int a = 0
 for i in range(10):
  a += i
 print(a)

%%cython 表明将cython内嵌到jupyter,cdef 是cython的关键字,用于定义c类型,这里将a定义为c中的int类型,并且初始化为0.

然后后面的循环就是累加0到9的意思,最后输出45.

另外,我们如果想分析代码 的执行情况,可以输入 %%cython --annotate 命令,这样就可以输出结果的同时,也输出 详细的代码执行情况报告了。

截图如图4 所示:

Python和C/C++交互的几种方法总结

图 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如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2017/5/8 9:35
# @Author : Lyrichu
# @Email : 919987476@qq.com
# @File : test.py
'''
@Description: 测试使用cython 混合c与python的integrate 函数与纯python写的integrate函数速度上的差异
'''
from integrate import integrate_f
import time

a = 1 # 积分区间下界
b = 2 # 积分区间上界
N = 10000 # 划分区间个数

# 使用纯python代码写的integrate函数
def py_f(x):
 return x**2 - x

def py_integrate_f(a,b,N):
 dx = (b-a)/N
 s = 0
 for i in range(N):
  s += py_f(a + i*dx)*dx
 return s

start_time1 = time.time()
integrate_f_res = integrate_f(a,b,N)
print("integrate_f_res = %s" % integrate_f_res)
end_time1 = time.time()
print(u"cython 版本计算耗时:%.8f" % (end_time1 - start_time1))

start_time2 = time.time()
py_integrate_f_res = py_integrate_f(a,b,N)
print("py_integrate_f_res = %s" % py_integrate_f_res)
end_time2 = time.time()
print(u"python 版本计算耗时:%.8f" % (end_time2 - start_time2))

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

Python和C/C++交互的几种方法总结

图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 一项,如下:

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文件如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2017/5/8 12:21
# @Author : Lyrichu
# @Email : 919987476@qq.com
# @File : test.py
'''
@Description: test file
'''
from calling_c import f_sin_squared,parse_char_to_int
str = "012"
str_b = bytes(str,encoding='utf-8')
n = parse_char_to_int(str_b)
print("n = %d" %n)
from math import pi,sqrt
x = sqrt(pi/2)
res = f_sin_squared(x)
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。本来想一起写的,想想还是分开写吧,不然太长了。后续会陆续更新,敬请关注。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
在Python中移动目录结构的方法
Jan 31 Python
python 编程之twisted详解及简单实例
Jan 28 Python
python数字图像处理之高级滤波代码详解
Nov 23 Python
Python二叉树定义与遍历方法实例分析
May 25 Python
Mac下Anaconda的安装和使用教程
Nov 29 Python
python实现得到当前登录用户信息的方法
Jun 21 Python
Python 如何优雅的将数字转化为时间格式的方法
Sep 26 Python
Python 多线程,threading模块,创建子线程的两种方式示例
Sep 29 Python
python 统计文件中的字符串数目示例
Dec 24 Python
Python selenium自动化测试模型图解
Apr 15 Python
python 调用Google翻译接口的方法
Dec 09 Python
一文搞懂python异常处理、模块与包
Jun 26 Python
Python常用时间操作总结【取得当前时间、时间函数、应用等】
May 11 #Python
详解Python读取配置文件模块ConfigParser
May 11 #Python
python如何获取服务器硬件信息
May 11 #Python
浅谈Python基础之I/O模型
May 11 #Python
老生常谈Python进阶之装饰器
May 11 #Python
python 第三方库的安装及pip的使用详解
May 11 #Python
插入排序_Python与PHP的实现版(推荐)
May 11 #Python
You might like
PHP脚本的10个技巧(5)
2006/10/09 PHP
Laravel 5框架学习之数据库迁移(Migrations)
2015/04/08 PHP
预加载css或javascript的js代码
2010/04/23 Javascript
JavaScript使用过程中需要注意的地方和一些基本语法
2010/08/26 Javascript
jquery调用wcf并展示出数据的方法
2011/07/07 Javascript
基于JavaScript实现继承机制之调用call()与apply()的方法详解
2013/05/07 Javascript
jQuery根据纬度经度查看地图处理程序
2013/05/08 Javascript
JS+DIV实现鼠标划过切换层效果的实例代码
2013/11/26 Javascript
45个JavaScript编程注意事项、技巧大全
2015/02/11 Javascript
js实现带农历和八字等信息的日历特效
2016/05/16 Javascript
js中获取时间new Date()的全面介绍
2016/06/20 Javascript
Vue.js组件使用开发实例教程
2016/11/01 Javascript
H5上传本地图片并预览功能
2017/05/08 Javascript
微信小程序使用slider设置数据值及switch开关组件功能【附源码下载】
2017/12/09 Javascript
Vue多系统切换实现方案
2018/06/05 Javascript
Vue动态控制input的disabled属性的方法
2018/06/26 Javascript
JavaScript引用类型Array实例分析
2018/07/24 Javascript
JS算法题之查找数字在数组中的索引位置
2019/05/15 Javascript
jquery 插件重新绑定的处理方法分析
2019/11/23 jQuery
JavaScript实现PC端横向轮播图
2020/02/07 Javascript
基于Vue sessionStorage实现保留搜索框搜索内容
2020/06/01 Javascript
python使用正则表达式分析网页中的图片并进行替换的方法
2015/03/26 Python
对于Python装饰器使用的一些建议
2015/06/03 Python
python中的字典使用分享
2016/07/31 Python
pandas 使用apply同时处理两列数据的方法
2018/04/20 Python
详解Django之admin组件的使用和源码剖析
2018/05/04 Python
Python爬取知乎图片代码实现解析
2019/09/17 Python
使用Python实现画一个中国地图
2019/11/23 Python
Python 中判断列表是否为空的方法
2019/11/24 Python
Pycharm自动添加文件头注释和函数注释参数的方法
2020/10/23 Python
Restful_framework视图组件代码实例解析
2020/11/17 Python
Jacques Lemans德国:奥地利钟表品牌
2019/12/26 全球购物
项目转让协议书
2014/10/27 职场文书
2014年银行年终工作总结
2014/12/19 职场文书
Python 类,对象,数据分类,函数参数传递详解
2021/09/25 Python
css3应用示例:新增的选择器
2022/03/16 HTML / CSS