第二章 编译和运行Cython代码

编译和运行Cython代码

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
#fib.pyx
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
## C函数库调用
如何使用 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) # note: atoi() has no error detection!

你可以在 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
# Python version >= 3.2 final ?
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
/*cfib.h*/
#ifndef __CFIB_H__
#define __CFIB_H__
double cfib(int n);
#endif
/*cfib.c*/
#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*/
#include "_fib.h"
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*/
#ifndef __FIB_H__
#define __FIB_H__
unsigned long int fib(unsigned long int);
#endif
#fib.pyx
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
#fib.pyxbld
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.pyxdeps
_fib.*
#user_pyximport.py
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++源码:

1
$ cython fib.pyx

第二步使用GCC编译C/C++源码,全部过程如下:

1
2
3
4
5
$ CFLAGS=$(python-config --cflags)
$ LDFLAGS=$(python-config --ldflags)
$ cython fib.pyx # --> outputs fib.c
$ gcc -c fib.c ${CFLAGS} # outputs fib.o
$ gcc fib.o -o fib.so -shared ${LDFLAGS} # --> outputs fib.so

手动编译对平台环境要求较高,不同的平台环境参数不一样

用其他构建系统使用Cython

CMake和Cython

1
2
3
4
5
6
7
8
# Detects and activates Cython
include(UseCython)
# Specifies that Cython source files should generate C++
set_source_files_properties(${CYTHON_CMAKE_EXAMPLE_SOURCE_DIR}/src/file.pyx PROPERTIES CYTHON_IS_CXX TRUE )
# Adds and compiles Cython source into an extension module
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
# test.py
from math import pi, e
print "e**pi == {:.2f}".format(e**pi)
print "pi**e == {:.2f}".format(pi**e)
#使用cython编译器将Python源码转换成C代码
$ cython --embed irrationals.py
#将C代码编译成可执行二进制
$ gcc $(python-config --cflags) $(python-config --ldflags) ./irrationals.c
#注意:该二进制执行还是依赖Python的动态链接库

编译指令

Cython提供了编译指令来控制如何编译Cython源码,指令能指定四个不同的作用域,并且能很容易的打开或者关闭测试或者Debug选项,但是并不是所有的指令都能设置每一个域。
所有的指令都可以在Cython代码的第一行通过注释来添加,如下:

1
2
3
4
5
# cython: nonecheck=True
# cython: boundscheck=False
也可以通过逗号分隔不同的指令:
# cython: nonecheck=True, boundscheck=False

当然,我们也可以通过命令行参数-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]

文章目录
  1. 1. 本章介绍几种Cython的编译方式让他可以同Python一起运行:
  2. 2. Cython编译管道(Pipeline)
  3. 3. 标准的方法:使用distutils和cythonize
    1. 3.1. 例子:将fib.pyx源码编译成Linux系统下的fib.so共享库或者Windows系统下的fib.pyd文件
  4. 4. 在IPython中和Cython进行交互
  5. 5. 使用pyximport在导入时 进行编译
  6. 6. 管理pyximport的依赖
    1. 6.1. 完整的依赖扩展的例子
  7. 7. 自己动手手动编译
  8. 8. 用其他构建系统使用Cython
    1. 8.1. CMake和Cython
    2. 8.2. SCons和Cython
    3. 8.3. Make和Cython
  9. 9. 将Python源码利用Cython编译成可执行二进制
  10. 10. 编译指令
|