- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议91:使用 Cython 编写扩展模块
Python-API让大家可以方便地使用C/C++编写扩展模块,从而通过重写应用中的瓶颈代码获得性能提升。但是,这种方式仍然有几个问题让Pythonistas非常头疼:
1)掌握C/C++编程语言、工具链有巨大的学习成本,如果没有这方面的技术积累,就无法快速编写代码,解决性能瓶颈。
2)即便是C/C++熟手,重写代码也有非常多的工作,比如编写特定数据结构、算法的C/C++版本,费时费力还容易出错。
所以整个Python社区都在努力实现一个“编译器”,它可以把Python代码直接编译成等价的C/C++代码,从而获得性能提升。通过开发人员的艰苦工作,涌现出了一批这类工具,如Pyrex、Py2C和Cython等。而从Pyrex发展而来的Cython是其中的集大成者。
Cython通过给Python代码增加类型声明和直接调用C函数,使得从Python代码中转换的C代码能够有非常高的执行效率。它的优势在于它几乎支持全部Python特性,也就是说,基本上所有的Python代码都是有效的Cython代码,这使得将Cython技术引入项目的成本降到最低。除此之外,Cython支持使用decorator语法声明类型,甚至支持专门的类型声明文件,以使原有的Python代码能够继续保持独立,这些特性都使它得到广泛应用,如PyAMF、PyYAML等库都使用它编写自己的高效率版本。
安装Cython非常简单,使用pip能够很方便地安装。
pip install –U cython
编译时间有点漫长,稍作等待,Cython就自动安装好了。然后我们可以尝试拿之前的arithmetic.py尝试一下,执行命令cython arithmetic.py,很快就完成了,但其实生成了一个arithmetic.c文件,它非常巨大,大概会有两三千行。是的,你没有看错,只有8行有效代码的arithmetic.py文件生成的C代码有两三千行。它的部分代码(subtract函数对应的代码的一分部)如下:
... /* Python wrapper */ static PyObject *__pyx_pw_10arithmetic_7subtract(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ static PyMethodDef __pyx_mdef_10arithmetic_7subtract = {__Pyx_NAMESTR("subtract"), (PyCFunction)__pyx_pw_10arithmetic_7subtract, METH_VARARGS|METH_KEYWORDS, __Pyx_DOCSTR(0)}; static PyObject *__pyx_pw_10arithmetic_7subtract(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_x = 0; PyObject *__pyx_v_y = 0; int __pyx_lineno = 0; const char *__pyx_filename = NULL; int __pyx_clineno = 0; PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("subtract (wrapper)", 0); { static PyObject **__pyx_pyargnames[] = {&__pyx_n_s__x,&__pyx_n_s__y,0}; PyObject* values[2] = {0,0}; ...
看不懂?没有关系,机器生成的代码本来就不是为了给人看的,还是把它交给编译器吧。
$ gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing \ -I/usr/include/python2.7 -o arithmetic.so arithmetic.c
又是一阵等待,编译、链接工作完成后,arithmethic.so文件就生成了。这时候可以像 import普通的Python模块一样使用它。
$ python >>> import arithmetic >>> arithmetic.subtract(2, 1) 1
每一次都需要编译、等待未免麻烦,所以Cython很体贴地提供了无需显式编译的方案:pyximport。只要将原有的Python代码后缀名从.py改为.pyx即可。
$ cp arithmetic.py arithmetic.pyx $ cd ~ $ python >>> import arithmetic Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named arithmetic >>> import pyximport; pyximport.install() (None, <pyximport.pyximport.PyxImporter object at 0x10fbd05d0>) >>> import arithmetic >>> arithmetic.__file__ '/Users/apple/.pyxbld/lib.macosx-10.8-x86_64-2.7/arithmetic.so'
从__file__属性可以看出,这个.pyx文件已经被编译链接为共享库了,pyximport的确方便啊!掌握了Cython的基本使用方法之后,就可以更进一步学习了。接下来要谈的是如何通过Cython把原有代码的性能提升许多倍,是的,Cython就是这么快!
在GIS中,经常需要计算地球表面上两点之间的距离。
import math def great_circle(lon1,lat1,lon2,lat2): radius = 3956 #miles x = math.pi/180.0 a = (90.0-lat1)*(x) b = (90.0-lat2)*(x) theta = (lon2-lon1)*(x) c = math.acos((math.cos(a)*math.cos(b)) + (math.sin(a)*math.sin(b)*math.cos(theta))) return radius*c
这段Python代码的执行效率可以通过timeit来确定。
import timeit lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826 num = 500000 t = timeit.Timer("p1.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2), "import p1") print "Pure python function", t.timeit(num), "sec"
执行50万次大概需要:2.2秒,太慢了。接下来尝试使用Cython进行改写,为了避免一下子代码变化太大,只使用Cython的类型声明“技能”,看看能达到什么效果。
import math def great_circle(float lon1,float lat1,float lon2,float lat2): cdef float radius = 3956.0 cdef float pi = 3.14159265 cdef float x = pi/180.0 cdef float a,b,theta,c a = (90.0-lat1)*(x) b = (90.0-lat2)*(x) theta = (lon2-lon1)*(x) c = math.acos((math.cos(a)*math.cos(b)) + (math.sin(a)*math.sin(b)*math.cos(theta))) return radius*c
通过给great_circle函数的参数、中间变量增加类型声明,Cython代码看起来跟原有的Python代码并无很大不同,业务逻辑代码一行没改。使用timeit的测定结果是大概1.8秒,提速将近二成,说明类型声明对性能提升非常有帮助。这时候,还有一个性能瓶颈需要解决,那就是:调用的math库是一个Python库,性能较差。解决这个问题,需要用到Cython的另一个技能:直接调用C函数。
cdef extern from "math.h": float cosf(float theta) float sinf(float theta) float acosf(float theta) def great_circle(float lon1,float lat1,float lon2,float lat2): cdef float radius = 3956.0 cdef float pi = 3.14159265 cdef float x = pi/180.0 cdef float a,b,theta,c a = (90.0-lat1)*(x) b = (90.0-lat2)*(x) theta = (lon2-lon1)*(x) c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta))) return radius*c
Cython使用cdef extern from语法,将math.h这个C语言库头文件里声明的cofs、sinf、acosf等函数导入代码中。因为减少了Python函数调用和调用时产生的类型转换开销,使用timeit测定这个版本的代码的效率仅需要大概0.4秒的时间,性能提升了5倍有余。
通过这个例子,可以掌握Cython的两大技能:类型声明和直接调用C函数。只要再进一步参考Cython的文档,就可以尝试在项目中使用了。比起直接使用C/C++编写扩展模块,使用Cython的方法方便得多,成本也更低。
注意
除了使用Cython编写扩展模块提升性能之外,Cython也可用来把之前编写的C/C++代码封装成.so模块给Python调用(类似boost.python/SWIG的功能),Cython社区已经开发了许多自动化工具。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论