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 [ ]:

  1. """ Example of wrapping cos function from math.h using Cython. """
  2. cdef extern from "math.h":
  3. double cos(double arg)
  4. def cos_func(arg):
  5. return cos(arg)

注意额外的关键词,比如cdefextern。同时,cos_func也是纯Python。

和前面一样,我们可以使用标准的distutils模块,但是,这次我们需要一些来自于Cython.Distutils的更多代码:

In [ ]:

  1. from distutils.core import setup, Extension
  2. from Cython.Distutils import build_ext
  3. setup(
  4. cmdclass={'build_ext': build_ext},
  5. ext_modules=[Extension("cos_module", ["cos_module.pyx"])]
  6. )

编译这个模块:

In [ ]:

  1. $ cd advanced/interfacing_with_c/cython
  2. $ ls
  3. cos_module.pyx setup.py
  4. $ python setup.py build_ext --inplace
  5. running build_ext
  6. cythoning cos_module.pyx to cos_module.c
  7. building 'cos_module' extension
  8. creating build
  9. creating build/temp.linux-x86_64-2.7
  10. gcc -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.o
  11. gcc -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
  12. $ ls
  13. build/ cos_module.c cos_module.pyx cos_module.so* setup.py

并且运行:

In [ ]:

  1. In [1]: import cos_module
  2. In [2]: cos_module?
  3. Type: module
  4. String Form:<module 'cos_module' from 'cos_module.so'>
  5. File: /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.so
  6. Docstring: <no docstring>
  7. In [3]: dir(cos_module)
  8. Out[3]:
  9. ['__builtins__',
  10. '__doc__',
  11. '__file__',
  12. '__name__',
  13. '__package__',
  14. '__test__',
  15. 'cos_func']
  16. In [4]: cos_module.cos_func(1.0)
  17. Out[4]: 0.5403023058681398
  18. In [5]: cos_module.cos_func(0.0)
  19. Out[5]: 1.0
  20. In [6]: cos_module.cos_func(3.14159265359)
  21. Out[6]: -1.0

并且,测试一下强壮性,我们可以看到我们得到了更好的错误信息:

In [ ]:

  1. In [7]: cos_module.cos_func('foo')
  2.  ------------- ------------- ------------- ------------- -----------------------
  3. TypeError Traceback (most recent call last)
  4. <ipython-input-7-11bee483665d> in <module>()
  5. ----> 1 cos_module.cos_func('foo')
  6. /home/esc/git-working/scipy-lecture-notes/advanced/interfacing_with_c/cython/cos_module.so in cos_module.cos_func (cos_module.c:506)()
  7. TypeError: a float is required

此外,不需要Cython完全传输到C math库的声明,上面的代码可以简化为:

In [ ]:

  1. """ Simpler example of wrapping cos function from math.h using Cython. """
  2. from libc.math cimport cos
  3. def cos_func(arg):
  4. return cos(arg)

在这种情况下,cimport语句用于导入cos函数。

2.8.5.2 Numpy支持

Cython通过numpy.pyx文件支持Numpy,允许你为你的Cython代码添加Numpy数组类型,即就像指定变量iint类型,你也可以指定变量a是带有给定的dtypenumpy.ndarray。同时,同时特定的优化比如边际检查也是支持的。看一下Cython文档的对应部分。如果你想要将Numpy数组作为C数组传递给Cython封装的C函数,在Cython wiki上有对应的部分。

在下面的例子中,我们将演示如何用Cython来封装类似的cos_doubles

In [ ]:

  1. void cos_doubles(double * in_array, double * out_array, int size);

In [ ]:

  1. #include <math.h>
  2. /* Compute the cosine of each element in in_array, storing the result in
  3. * out_array. */
  4. void cos_doubles(double * in_array, double * out_array, int size){
  5. int i;
  6. for(i=0;i<size;i++){
  7. out_array[i] = cos(in_array[i]);
  8. }
  9. }

这个函数使用下面的Cython代码来封装cos_doubles_func:

In [ ]:

  1. """ Example of wrapping a C function that takes C double arrays as input using
  2. the Numpy declarations from Cython """
  3. # cimport the Cython declarations for numpy
  4. cimport numpy as np
  5. # if you want to use the Numpy-C-API from Cython
  6. # (not strictly necessary for this example, but good practice)
  7. np.import_array()
  8. # cdefine the signature of our c function
  9. cdef extern from "cos_doubles.h":
  10. void cos_doubles (double * in_array, double * out_array, int size)
  11. # create the wrapper code, with numpy type annotations
  12. def cos_doubles_func(np.ndarray[double, ndim=1, mode="c"] in_array not None,
  13. np.ndarray[double, ndim=1, mode="c"] out_array not None):
  14. cos_doubles(<double*> np.PyArray_DATA(in_array),
  15. <double*> np.PyArray_DATA(out_array),
  16. in_array.shape[0])

可以使用distutils来编译:

In [ ]:

  1. from distutils.core import setup, Extension
  2. import numpy
  3. from Cython.Distutils import build_ext
  4. setup(
  5. cmdclass={'build_ext': build_ext},
  6. ext_modules=[Extension("cos_doubles",
  7. sources=["_cos_doubles.pyx", "cos_doubles.c"],
  8. include_dirs=[numpy.get_include()])],
  9. )

与前面的编译Numpy例子类似,我们需要include_dirs选项。

In [ ]:

  1. $ ls
  2. cos_doubles.c cos_doubles.h _cos_doubles.pyx setup.py test_cos_doubles.py
  3. $ python setup.py build_ext -i
  4. running build_ext
  5. cythoning _cos_doubles.pyx to _cos_doubles.c
  6. building 'cos_doubles' extension
  7. creating build
  8. creating build/temp.linux-x86_64-2.7
  9. gcc -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.o
  10. In file included from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1722,
  11. from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:17,
  12. from /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/arrayobject.h:15,
  13. from _cos_doubles.c:253:
  14. /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"
  15. /home/esc/anaconda/lib/python2.7/site-packages/numpy/core/include/numpy/__ufunc_api.h:236: warning: _import_umath defined but not used
  16. gcc -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.o
  17. gcc -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
  18. $ ls
  19. build/ _cos_doubles.c cos_doubles.c cos_doubles.h _cos_doubles.pyx cos_doubles.so* setup.py test_cos_doubles.py

和前面一样,我们来验证一下它是有效的:

In [ ]:

  1. import numpy as np
  2. import pylab
  3. import cos_doubles
  4. x = np.arange(0, 2 * np.pi, 0.1)
  5. y = np.empty_like(x)
  6. cos_doubles.cos_doubles_func(x, y)
  7. pylab.plot(x, y)
  8. pylab.show()

2.8.5 Cython - 图1

2.8.6 总结

这个部分演示了四种与原生代码交互的技术。下表概述了这些技术的一些方面。

xPart of CPythonCompiledAutogeneratedNumpy Support
Python-C-APITrueTrueFalseTrue
CtypesTrueFalseFalseTrue
SwigFalseTrueTrueTrue
CythonFalseTrueTrueTrue

在上面的技术中,Cython是最现代最高级的。特别是,通过为Python代码添加类型来增量优化代码的技术是惟一的。