基础教程
Cython 的基础
Cython 的本质可以总结如下:Cython 是包含 C 数据类型的 Python。
Cython 是 Python:几乎所有 Python 代码都是合法的 Cython 代码。 (存在一些限制,但是差不多也可以。) Cython 的编译器会转化 Python 代码为 C 代码,这些 C 代码均可以调用 Python/C 的 API。
Cython 可不仅仅包含这些,Cython 中的参数和变量还可以以 C 数据类型来声明。代码中的 Python 值和 C 的值可以自由地交叉混合(intermixed)使用, 所有的转化都是自动进行。Python 中的引用计数维护(Reference count maintenance)和错误检查(error checking)操作同样是自动进行的,并且全面支持 Python 的异常处理工具(facilities),包括 try-except
和 try-finally
,即便在其中操作 C 数据都是可以的。
Cython 的 Hello World
由于 Cython 能接受几乎所有的合法 Python 源文件,开始使用 Cython 的最难的事情之一是怎么编译你的拓展(extension)。
那么,让我们从典型的(canonical)Python hello world
开始:
print "Hello World"
将代码保存在文件 helloworld.pyx
中。现在,我们需要创建 setup.py
,它是一个类似 Python Makefile 的文件(更多信息请看源文件和编译过程)。 你编写的 setup.py
应该看起来类似这样:
from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("helloworld.pyx")
)
输入如下命令来构建你的 Cython 文件:
$ python setup.py build_ext --inplace
运行完上述命令会在你的当前目录生成一个新文件,如果你的系统是 Unix,文件名为 helloworld.so
,如果你的系统是 Windows,文件名为 helloworld.pyd
。现在我们用一用刚生成的文件:打开 Python 的解释器(interpreter),像 import 普通文件一样直接 import
你刚生成的文件:
>>> import helloworld
Hello World
恭喜!你已经学会了怎样构建 Cython 的拓展了。但是到现在为止,这个例子并没有给我们一个使用 Cython 的理由。所以,让我们创建一个更现实的例子。
pyximport:Cython 简单编译
如果你的模块不需要额外的 C 库活特殊的构件安装,那你可以在 import
时使用 Paul Prescod 和 Stefan Behnel 编写的 pyximport
模块来直接读取 .pyx
文件,而不需要编写 setup.py
文件。 它随同 Cython 一并发布和安装,你可以这样使用它:
>>> import pyximport; pyximport.install()
>>> import helloworld
Hello World
自 Cython 0.11 起,pyximport
模块同样实验性地支持普通 Python 模块的编译了。它允许你在所有 Python import 的 .pyx
和 .py
模块上自动运行 Cython,包括哪些标准库和第三方库。但是,任然有不少 Python 模块 Cython 无法编译,遇到这种情况 import 机制(mechanism)会退回去读取 Python 原模块。.py
的 import 机制可按如下方式安装:
>>> pyximport.install(pyimport = True)
斐波那契(Fibonacci)函数
Python 的官方教程中斐波那契函数是这样定义的:
def fib(n):
"""Print the Fibonacci series up to n."""
a, b = 0, 1
while b < n:
print b,
a, b = b, a + b
现在,我们模仿 Hello World 例子中的步骤,第一步将 Python 官方教程中斐波那契函数文件名改为.pyx
,例如 fib.pyx
,然后我们创建 setup.py
文件。你只需要修改一下 Cython 文件的文件名和生成模块的名字就可以直接复用 Hello World 例子中的 setup.py
文件。这样,我们有了这么个文件:
from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize("fib.pyx"),
)
使用和 helloworld.pyx
一样的命令来构建该拓展:
$ python setup.py build_ext --inplace
使用新的拓展:
>>> import fib
>>> fib.fib(2000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
质数(Primes)
本段给出一个小例子来展示一些我们可以做的事。这个例子给出一个用来寻找质数的程序。你告诉它你需要多少个质数,程序以 Python list
的形式将这些质数返回给你。
primes.pyx
:
def primes(int kmax):
cdef int n, k, i
cdef int p[1000]
result = []
if kmax > 1000:
kmax = 1000
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p[i] != 0:
i = i + 1
if i == k:
p[k] = n
k = k + 1
result.append(n)
n = n + 1
return result
可以从上面的代码中看出,除了参数 kmax
是以 int
类型声明的外,代码和普通 Python 函数定义一样。这意味着传入 kmax
的对象将会转化成 C 语言的整数变量。(如果无法转化为 int
型,将会抛出 TypeError
异常)。
第 2、3 行使用了 cdef
来定义 C 语言的局部变量。第 4 行创建了一个用来返回结果的 Python list
。注意,代码的编写和 Python 代码的编写一模一样。因为结果变量还没给定类型,它只是用来储存 Python 对象。
第 7-9 行配置了一个循环用来测试候选数字是否是质数,一直到找到了足够多的质数为止。第 11-12 行用候选数字除以已经找到的质数,这两行很有意思. 因为没有涉及 Python 对象,所以循环将会完全翻译为 C 语言代码,所以运行非常快!
当一个质数被找到,第 14-15 行会将其添加到队列 p
中,以便在循环中检测质数时用来快速检索,第 16 行将其添加到结果队列中。第 16 行看起来也非常类似 Python 代码,它也的确是 Python 代码,在 twist
的作用下 C 语言定义的变量 n
在 append
方法调用前会自动转化为 Python 对象。最后,在第 18 行通过普通的 Python return
命令返回结果队列。
使用 Cython 编译器编译 primes.pyx
文件来生成一个拓展模块,我们可以在互动解释器(interactive interpreter)中来试用:
>>> import primes
>>> primes.primes(10)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
正常运行!如果你好奇 Cython 为你节约了多少资源,可以看看这个模块生成的 C 代码。
语言细节
想要获取更多地 Cython 语言信息,请看 Language Basics。 想在数字计算中运用 Cython 请看 Cython for NumPy Users。