2.8.5 Cython
Cython既是写C扩展的类Python语言,也是这种语言的编译器。Cython语言是Python的超集,带有额外的结构,允许你调用C函数和C类型的注释变量和类属性。在这个意义上,可以称之为带有类型的Python。
除了封装原生代码的基础应用案例,Cython也支持额外的应用案例,即交互优化。从根本上来说,从纯Python脚本开始,向瓶颈代码逐渐增加Cython类型来优化那些确实有影响的代码。
在这种情况下,与SWIG很相似,因为代码可以自动生成,但是,从另一个角度来说,又与ctypes很类似,因为,封装代码(大部分)是用Python写的。
尽管其他自动生成代码的解决方案很难debug(比如SWIG),Cython有一个GNU debugger扩展来帮助debug Python,Cython和C代码。
注意 自动生成的C代码使用Python-C-Api。
优点
- 类Python语言来写扩展
- 自动生成代码
- 支持增量优化
- 包含一个GNU debugger扩展
- 支持C++ (从版本0.13) 不足
- 必须编译
- 需要额外的库 ( 只是在build的时候, 在这个问题中,可以通过运送生成的C文件来克服)
2.8.5.1 例子
cos_module的主要Cython代码包含在文件cos_module.pyx中:
In [ ]:
""" Example of wrapping cos function from math.h using Cython. """cdef extern from "math.h":double cos(double arg)def cos_func(arg):return cos(arg)
注意额外的关键词,比如cdef和extern。同时,cos_func也是纯Python。
和前面一样,我们可以使用标准的distutils模块,但是,这次我们需要一些来自于Cython.Distutils的更多代码:
In [ ]:
from distutils.core import setup, Extensionfrom Cython.Distutils import build_extsetup(cmdclass={'build_ext': build_ext},ext_modules=[Extension("cos_module", ["cos_module.pyx"])])
编译这个模块:
In [ ]:
$ cd advanced/interfacing_with_c/cython$ lscos_module.pyx setup.py$ python setup.py build_ext --inplacerunning build_extcythoning cos_module.pyx to cos_module.cbuilding 'cos_module' extensioncreating buildcreating build/temp.linux-x86_64-2.7gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/include/python2.7 -c cos_module.c -o build/temp.linux-x86_64-2.7/cos_module.ogcc -pthread -shared build/temp.linux-x86_64-2.7/cos_module.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.so$ lsbuild/ cos_module.c cos_module.pyx cos_module.so* setup.py
并且运行:
In [ ]:
In [1]: import cos_moduleIn [2]: cos_module?Type: moduleString Form:<module 'cos_module' from 'cos_module.so'>File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.soDocstring: <no docstring>In [3]: dir(cos_module)Out[3]:['__builtins__','__doc__','__file__','__name__','__package__','__test__','cos_func']In [4]: cos_module.cos_func(1.0)Out[4]: 0.5403023058681398In [5]: cos_module.cos_func(0.0)Out[5]: 1.0In [6]: cos_module.cos_func(3.14159265359)Out[6]: -1.0
并且,测试一下强壮性,我们可以看到我们得到了更好的错误信息:
In [ ]:
In [7]: cos_module.cos_func('foo')------------- ------------- ------------- ------------- -----------------------TypeError Traceback (most recent call last)<ipython-input-7-11bee483665d> in <module>()----> 1 cos_module.cos_func('foo')/home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.so in cos_module.cos_func (cos_module.c:506)()TypeError: a float is required
此外,不需要Cython完全传输到C math库的声明,上面的代码可以简化为:
In [ ]:
""" Simpler example of wrapping cos function from math.h using Cython. """from libc.math cimport cosdef cos_func(arg):return cos(arg)
在这种情况下,cimport语句用于导入cos函数。
2.8.5.2 Numpy支持
Cython通过numpy.pyx文件支持Numpy,允许你为你的Cython代码添加Numpy数组类型,即就像指定变量i是int类型,你也可以指定变量a是带有给定的dtype的numpy.ndarray。同时,同时特定的优化比如边际检查也是支持的。看一下Cython文档的对应部分。如果你想要将Numpy数组作为C数组传递给Cython封装的C函数,在Cython wiki上有对应的部分。
在下面的例子中,我们将演示如何用Cython来封装类似的cos_doubles。
In [ ]:
void cos_doubles(double * in_array, double * out_array, int size);
In [ ]:
#include <math.h>/* Compute the cosine of each element in in_array, storing the result in* out_array. */void cos_doubles(double * in_array, double * out_array, int size){int i;for(i=0;i<size;i++){out_array[i] = cos(in_array[i]);}}
这个函数使用下面的Cython代码来封装cos_doubles_func:
In [ ]:
""" Example of wrapping a C function that takes C double arrays as input usingthe Numpy declarations from Cython """# cimport the Cython declarations for numpycimport numpy as np# if you want to use the Numpy-C-API from Cython# (not strictly necessary for this example, but good practice)np.import_array()# cdefine the signature of our c functioncdef extern from "cos_doubles.h":void cos_doubles (double * in_array, double * out_array, int size)# create the wrapper code, with numpy type annotationsdef cos_doubles_func(np.ndarray[double, ndim=1, mode="c"] in_array not None,np.ndarray[double, ndim=1, mode="c"] out_array not None):cos_doubles(<double*> np.PyArray_DATA(in_array),<double*> np.PyArray_DATA(out_array),in_array.shape[0])
可以使用distutils来编译:
In [ ]:
from distutils.core import setup, Extensionimport numpyfrom Cython.Distutils import build_extsetup(cmdclass={'build_ext': build_ext},ext_modules=[Extension("cos_doubles",sources=["_cos_doubles.pyx", "cos_doubles.c"],include_dirs=[numpy.get_include()])],)
与前面的编译Numpy例子类似,我们需要include_dirs选项。
In [ ]:
$ lscos_doubles.c cos_doubles.h _cos_doubles.pyx setup.py test_cos_doubles.py$ python setup.py build_ext -irunning build_extcythoning _cos_doubles.pyx to _cos_doubles.cbuilding 'cos_doubles' extensioncreating buildcreating build/temp.linux-x86_64-2.7gcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c _cos_doubles.c -o build/temp.linux-x86_64-2.7/_cos_doubles.oIn file included from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1722,from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:17,from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/arrayobject.h:15,from _cos_doubles.c:253:/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/npy_deprecated_api.h:11:2: warning: #warning "Using deprecated NumPy API, disable it by #defining NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION"/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/__ufunc_api.h:236: warning: ‘_import_umath’ defined but not usedgcc -pthread -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include -I/home/esc/anaconda/include/python2.7 -c cos_doubles.c -o build/temp.linux-x86_64-2.7/cos_doubles.ogcc -pthread -shared build/temp.linux-x86_64-2.7/_cos_doubles.o build/temp.linux-x86_64-2.7/cos_doubles.o -L/home/esc/anaconda/lib -lpython2.7 -o /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython_numpy/cos_doubles.so$ lsbuild/ _cos_doubles.c cos_doubles.c cos_doubles.h _cos_doubles.pyx cos_doubles.so* setup.py test_cos_doubles.py
和前面一样,我们来验证一下它是有效的:
In [ ]:
import numpy as npimport pylabimport cos_doublesx = np.arange(0, 2 * np.pi, 0.1)y = np.empty_like(x)cos_doubles.cos_doubles_func(x, y)pylab.plot(x, y)pylab.show()

2.8.6 总结
这个部分演示了四种与原生代码交互的技术。下表概述了这些技术的一些方面。
| x | Part of CPython | Compiled | Autogenerated | Numpy Support |
|---|---|---|---|---|
| Python-C-API | True | True | False | True |
| Ctypes | True | False | False | True |
| Swig | False | True | True | True |
| Cython | False | True | True | True |
在上面的技术中,Cython是最现代最高级的。特别是,通过为Python代码添加类型来增量优化代码的技术是惟一的。