Cython在运行之前必须要编译,编译可以是隐式的或者是显式的
本章介绍几种Cython的编译方式让他可以同Python一起运行:
Cython代码能够被编译并且在IPython中交互运行
Cython代码可以在导入的时候自动编译
Cython可以通过构建工具单独的编译
Cython可以结合到标准的构建系统中,如make, CMake或者SCons
Cython编译管道(Pipeline) 管道的左右是转换Cython代码到Python的扩展模块,让其能被Python的解释器导入和使用。管道编译有两个步骤,第一个步骤是通过cython编译器转换Cython源码成优化过的平台独立的C或者C++代码,第二个步骤是通过标准的C或者C++编译器将生成的C或者C++源码编译成共享库。编译后的共享库是平台相关性的,在Linux或者Mac OS X系统下是.so扩展名的共享库,在Windows系统下是.pyd扩展名的动态链接库。
标准的方法:使用distutils和cythonize 通过使用Python的distutils模块和Cython的cythonize命令,显示的进行编译,也是最常用的方法。
例子:将fib.pyx源码编译成Linux系统下的fib.so共享库或者Windows系统下的fib.pyd文件 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def fib (int n) :
cdef int i
cdef double a=0.0 , b=1.0
for i in range(n):
a, b = a + b, a
return a
如何使用 Cython 调用 C 库函数?简单来说,我们先以一个 C 标准库中的函数为例。 你不需要向你的代码中引入 额外的依赖,Cython 都已经帮你定义好了这些函数。所以你可以将这些函数直接 cimport 进来并使用。
举个例子,比如说当你想用最简单的方法将char*类型的值转化为一个整型值时, 你可以使用atoi() 函数,这个函数是在stdlib.h 头文件中定义的。我们可以这样来写:
```python
from libc.stdlib cimport atoi
cdef parse_charptr_to_py_int(char* s):
assert s is not NULL, "byte string value is NULL"
return atoi(s)
你可以在 Cython 的源代码包https://github.com/cython/cython/tree/master/Cython/Includes中找到所有的标准 cimport 文件。这些文件保存在.pxd 文件中,这是一种标准再模块间共享 Cython 函数声明的方法。 Cython 也有一整套的 Cython 的C-API 函数声明集。 例如,为了测试你的 Cython 代码的 C 编译时间,你可以这样做:1
2
3
4
from cpython.version cimport PY_VERSION_HEX
print PY_VERSION_HEX >= 0x030200F0
Cython 也提供了 C math 库的声明:1
2
3
4
from libc.math cimport sin
cdef double f(double x):
return sin(x*x)
#setup.py from distutils.core import setup from Cython.Build import cythonize
setup(ext_modules=cythonize(‘fib.pyx’))
#通过命令行编译 $ python setup.py build_ext –inplace 等价于 $ python setup.py build_ext -i
#Windows系统要指定额外的参数 python setup.py build_ext -i –compiler=mingw32 -DMS_WIN64 或者 python setup.py build_ext -i –compiler=msvc 根据编译器的不同填写参数
#直接可以在IPython中import fib使用该模块1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
### 例子:使用Cython包装C和C++代码
```python
#ifndef __CFIB_H__
#define __CFIB_H__
double cfib(int n);
#endif
#include "cfib.h"
double cfib(int n) {
int i;
double a=0.0 , b=1.0 , tmp;
for (i=0 ; i<n; ++i) {
tmp = a; a = a + b; b = tmp;
}
return a;
}
#wrap_fib.pyx
cdef extern from "cfib.h" :
double cfib(int n)
def fib(n):
'' ' Returns the nth Fibonacci number.' ''
return cfib(n)
#setup.py
from distutils.core import setup , Extension
from Cython.Build import cythonize
exts = cythonize([Extension("wrap_fib" , sources=["cfib.c" , "wrap_fib.pyx" ])])
setup (ext_modules=cythonize(ext))
#如果提供的是预编译后的动态链接库libfib.so而不是源码,则setup .py如下
from distutils.core import setup , Extension
from Cython.Build import cythonize
ext = Extension( name="wrap_fib" , sources=["wrap_fib.pyx" ], library_dirs=["/path/to/libfib.so" ], libraries=["fib" ] )
setup (ext_modules=cythonize(ext))
在IPython中和Cython进行交互 在IPython中可以通过魔法命令%load_ext Cython和%%cython与Cython代码进行交互1
2
3
4
5
6
7
8
9
10
11
12
13
In [3 ]: %load_ext Cython
In [4 ]: %%cython
...: def fib (int n) :
...: cdef int i
...: cdef double a=0.0 , b=1.0
...: for i in range(n):
...: a, b = a+b, a
...: return a
...:
In [5 ]: fib(10 )
Out[5 ]: 55.0
除了%%cython命令外还有%%cython_inline和%%cython_pyximport两个命令可以使用。
使用pyximport在导入时 进行编译 pyximport在import时识别.pyx扩展模块,然后自动将他们编译后导入1
2
3
4
5
6
7
8
9
In [1 ]: import pyximport
In [2 ]: pyximport.install()
Out[2 ]: (None , <pyximport.pyximport.PyxImporter at 0x7fc79e1ee588 >)
In [3 ]: import fib
In [4 ]: fib.__file__
Out[4 ]: '/home/dj/.pyxbld/lib.linux-x86_64-3.5/fib.cpython-35m-x86_64-linux-gnu.so'
由于pyximport依赖cython编译器和C编译器,往往生产环境都不在控制中。
管理pyximport的依赖
有时候Cython源文件依赖其他的源文件如C、C++源代码,头文件或者其他的Cython源代码,在这种情况下当依赖文件更新的时候pyximport必须重新编译导入.pyx文件,这个时候可以使用.pyxdeps扩展名的文件,来列出.pyx所依赖的所有文件,文件内容可以是通配符也可以是依赖文件的列表。如果.pyxdeps文件存在,pyximport在导入的时候会比较.pyx的的修改时间,将其重新编译导入。
怎样告诉pyximport编译和链接几个源文件到一个扩展模块,这个时候需要.pyxbld文件,其目的是为不同的情况定制pyximport
像.pyxdeps和.pyxbld文件都是基于相同文件名的Cython的.pyx源代码,用.pyxbld替换.pyx扩展,他们应该被放置在被导入的.pyx的文件的同一个目录
.pyxbld文件中的是什么内容:一个或者两个函数
make_ext(modname, pyxfilename):如果定义了该函数,该函数接受两个参数,他返回一个distutils.extension.Extension实例,或者等效于调用Cython.Build.cythonize返回的结果,允许用户定制Extension,如下:
1
2
3
4
5
def make_ext (modname, pyxfilename) :
from distutils.extension import Extension
return Extension(modname,
sources=[pyxfilename, '_fib.c' ],
include_dirs = ['.' ])
make_setup_args():如果这个函数定义了,pyximport获取额外的参数传给distutils.core.setup,用于控制setup的编译过程,如下:
1
2
def make_setup_args () :
return dict(script_args=["--compiler=mingw32" ])
完整的依赖扩展的例子 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*_fib.c*/
unsigned long int fib(unsigned long int n)
{
unsigned long int a, b, tmp, i;
a = 0 ; b = 1 ;
for (i=0 ; i<n; ++i) {
tmp = a; a += b; b = tmp;
}
return a;
}
/*_fib.h*/
unsigned long int fib(unsigned long int);
cdef extern from "_fib.h" :
unsigned long int _cfib "fib" (unsigned long int)
def cfib (unsigned long int n) :
return _cfib(n)
def fib (long n) :
'''Returns the nth Fibonacci number.'''
cdef long a=0 , b=1 , i
for i in range(n):
a, b = a + b, a
return a
def make_ext (modname, pyxfilename) :
from distutils.extension import Extension
return Extension(modname,
sources=[pyxfilename, '_fib.c' ],
include_dirs = ['.' ])
def make_setup_args () :
return dict(script_args=["--compiler=mingw32" ])
_fib.*
import pyximport
pyximport.install()
import fib
print("fib.__file__" , fib.__file__)
print("fib.fib(90):" , fib.fib(90 ))
print("fib.cfib(90):" , fib.cfib(90 ))
自己动手手动编译 我们想从fib.pyx源代码创建一个扩展模块,但是不想使用distutils、IPython或者是pyximport,我们该怎么办? 这里有两步可以做到:第一步从Cython源码生成C/C++源码,第二步编译C/C++源码为一个扩展模块。 第一步很简单,用cython命令,将Cython源码转换成C/C++源码:
第二步使用GCC编译C/C++源码,全部过程如下:1
2
3
4
5
$ CFLAGS=$(python-config --cflags)
$ LDFLAGS=$(python-config --ldflags)
$ cython fib.pyx
$ gcc -c fib.c ${CFLAGS}
$ gcc fib.o -o fib.so -shared ${LDFLAGS}
手动编译对平台环境要求较高,不同的平台环境参数不一样
用其他构建系统使用Cython CMake和Cython 1
2
3
4
5
6
7
8
include(UseCython)
set_source_files_properties(${CYTHON_CMAKE_EXAMPLE_SOURCE_DIR}/src/file.pyx PROPERTIES CYTHON_IS_CXX TRUE )
cython_add_module( modname file.pyx cpp_source.cxx)
SCons和Cython Make和Cython 1
2
3
INCDIR := $(shell python -c "from distutils import sysconfig; print(sysconfig.get_python_inc())" )
LIBS := $(shell python -c "from distutils import sysconfig; print(sysconfig.get_config_var('LIBS'))" )
将Python源码利用Cython编译成可执行二进制 1
2
3
4
5
6
7
8
9
10
11
from math import pi, e
print "e**pi == {:.2f}" .format(e**pi)
print "pi**e == {:.2f}" .format(pi**e)
$ cython --embed irrationals.py
$ gcc $(python-config --cflags) $(python-config --ldflags) ./irrationals.c
编译指令 Cython提供了编译指令来控制如何编译Cython源码,指令能指定四个不同的作用域,并且能很容易的打开或者关闭测试或者Debug选项,但是并不是所有的指令都能设置每一个域。 所有的指令都可以在Cython代码的第一行通过注释来添加,如下:
当然,我们也可以通过命令行参数-X或者–directive选项来设置编译指令,如下:1
$ cython --directive nonecheck=False source.pyx
有些编译指令支持函数和上下文级别的作用域的控制,如装饰器和上下文管理器,实例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def fast_indexing () :
cimport cython
def fast_indexing (a) :
with cython.boundscheck(False ), cython.wraparound(False ):
for i in range(len(a)):
sum += a[i]