基础教程

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-excepttry-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 语言定义的变量 nappend 方法调用前会自动转化为 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