Cython 和 Pyrex 之间的区别

警告

Cython 和 Pyrex 都是不断变化的目标。列出这两个项目之间所有差异的明确列表将非常繁琐且难以跟踪,但希望这份高级列表能让人了解存在的差异。需要注意的是,这两个项目都在努力实现相互兼容,但 Cython 的目标是尽可能接近并完整地实现 Python。

Python 3 支持

Cython 创建 .c 文件,这些文件可以与 Python 2.x 和 Python 3.x 一起构建和使用。实际上,使用 Cython 编译模块可能是将代码移植到 Python 3 的一种简单方法。

Cython 还支持 Python 3.0 及更高版本的主要 Python 版本中包含的各种语法扩展。如果它们不与现有的 Python 2.x 语法或语义冲突,编译器通常会接受它们。其他一切取决于编译器指令 language_level=3(请参阅 编译器指令)。

列表/集合/字典推导式

Cython 支持 Python 3 为列表、集合和字典定义的不同推导式。

[expr(x) for x in A]             # list
{expr(x) for x in A}             # set
{key(x) : value(x) for x in A}   # dict

如果 A 是列表、元组或字典,则循环将被优化。您也可以使用 forfrom 语法,但通常建议使用通常的 forin range(...) 语法,并使用 C 运行变量(例如 cdef int i)。

注意

请参阅 自动范围转换

请注意,Cython 从 Python 2.4 开始也支持集合字面量。

仅限关键字参数

Python 函数可以具有仅限关键字的参数,这些参数列在 * 参数之后,并在 ** 参数之前(如果有),例如

def f(a, b, *args, c, d = 42, e, **kwds):
    ...

此处,cde 不能作为位置参数传递,必须作为关键字参数传递。此外,ce 是必需的关键字参数,因为它们没有默认值。

如果 * 后的参数名称被省略,则该函数将不接受任何额外的位置参数,例如

def g(a, b, *, c, d):
    ...

正好接受两个位置参数,并具有两个必需的关键字参数。

条件表达式“x if b else y”

条件表达式,如 https://www.pythonlang.cn/dev/peps/pep-0308/ 中所述

X if C else Y

仅评估 XY 之一(取决于 C 的值)。

cdef inline

模块级函数现在可以声明为内联函数,使用 inline 关键字传递给 C 编译器。这些函数可以与宏一样快。

cdef inline int something_fast(int a, int b):
    return a*a + b

请注意,类级别的 cdef 函数通过虚拟函数表处理,因此编译器几乎无法在大多数情况下内联它们。

声明时的赋值(例如“cdef int spam = 5”)

在 Pyrex 中,必须编写

cdef int i, j, k
i = 2
j = 5
k = 7

现在,使用 Cython,可以编写

cdef int i = 2, j = 5, k = 7

右侧的表达式可以任意复杂,例如

cdef int n = python_call(foo(x,y), a + b + c) - 32

for 循环中的“by”表达式(例如“for i from 0 <= i < 10 by 2”)

for i from 0 <= i < 10 by 2:
    print i

产生

0
2
4
6
8

注意

不建议使用此语法,因为它与正常的 Python for 循环冗余。请参阅 自动范围转换

布尔 int 类型(例如,它像 c int 一样工作,但强制转换为/从 python 转换为布尔值)

在 C 中,int 用于真值。在 python 中,任何对象都可以用作真值(使用 __nonzero__() 方法),但规范的选择是两个布尔对象 TrueFalsebint(表示“布尔 int”)类型编译为 C int,但强制转换为和从 Python 转换为布尔值。比较和几个内置函数的返回值也是 bint。这减少了将事物包装在 bool() 中的需要。例如,可以编写

def is_equal(x):
    return x == y

这将在 Pyrex 中返回 10,但在 Cython 中返回 TrueFalse。可以将变量和函数的返回值声明为 bint 类型。例如

cdef int i = x
cdef bint b = x

第一次转换将通过 x.__int__() 进行,而第二次转换将通过 x.__bool__()(又名 __nonzero__())进行,并对已知的内置类型进行适当的优化。

可执行类体

包括一个工作 classmethod()

cdef class Blah:
    def some_method(self):
        print self
    some_method = classmethod(some_method)
    a = 2*3
    print "hi", a

cpdef 函数

Cython 在通常的 defcdef 之上添加了第三种函数类型。如果函数被声明为 cpdef,则可以从扩展和普通 python 子类中调用它并被它们覆盖。本质上,可以将 cpdef 方法视为 cdef 方法 + 一些额外内容。(至少在实现上就是这样。)首先,它创建一个 def 方法,该方法只调用底层的 cdef 方法(如果需要,还会进行参数解包/强制转换)。在 cdef 方法的顶部,添加了一些代码来查看它是否被覆盖,类似于以下伪代码

if hasattr(type(self), '__dict__'):
    foo = self.foo
    if foo is not wrapper_foo:
        return foo(args)
[cdef method body]

要检测一个类型是否具有字典,它只需要检查 tp_dictoffset 槽位,该槽位对于扩展类型默认情况下为 NULL,但对于实例类则不为 NULL。如果字典存在,它会进行一次属性查找,并可以通过比较指针来判断返回的结果是否为一个新的函数。如果且仅当它是一个新的函数时,才会将参数打包成一个元组并调用该方法。这整个过程非常快。如果直接在类上调用该方法,则会设置一个标志,以防止进行此查找,例如:

cdef class A:
    cpdef foo(self):
        pass

x = A()
x.foo()  # will check to see if overridden
A.foo(x) # will call A's implementation whether overridden or not

有关解释和使用技巧,请参阅 为了速度而进行的早期绑定

自动范围转换

i 是任何 cdef 定义的整数类型,并且可以确定方向(即步长的符号)时,这将把形式为 for i in range(...) 的语句转换为 for i from ...

警告

如果范围导致对 i 的赋值溢出,这可能会改变语义。具体来说,如果设置了此选项,则会在进入循环之前引发错误,而没有此选项,循环将一直执行到遇到溢出值为止。如果这影响到你,请更改 Cython/Compiler/Options.py(最终将会有更好的方法来设置它)。

更友好的类型转换

在 Pyrex 中,如果输入 <int>x,其中 x 是一个 Python 对象,则会得到 x 的内存地址。同样,如果输入 <object>i,其中 i 是一个 C 整数,则会得到内存位置 i 处的“对象”。这会导致令人困惑的结果和段错误。

在 Cython 中,<type>x 将尝试进行强制转换(就像将 x 赋值给类型为 type 的变量时会发生的那样),前提是两种类型中只有一种是 Python 对象。它不会阻止你在没有转换的情况下进行转换(尽管它会发出警告)。如果你真的想要地址,请先转换为 void *

与 Pyrex 一样,<MyExtensionType>x 将在没有任何类型检查的情况下将 x 转换为类型 MyExtensionType。Cython 支持语法 <MyExtensionType?> 来进行带有类型检查的转换(即,如果 x 不是 MyExtensionType 的(子类),则会抛出错误)。

cdef/cpdef 函数中的可选参数

Cython 现在支持 cdefcpdef 函数的可选参数。

.pyx 文件中的语法与 Python 中保持一致,但你可以在 .pxd 文件中通过编写 cdef foo(x=*) 来声明此类函数。在子类化时,参数数量可能会增加,但参数类型和顺序必须保持一致。在某些情况下,当没有可选参数的 cdef/cpdef 函数被具有默认参数值的函数覆盖时,会存在轻微的性能损失。

例如,您可以拥有 .pxd 文件

cdef class A:
    cdef foo(self)

cdef class B(A):
    cdef foo(self, x=*)

cdef class C(B):
    cpdef foo(self, x=*, int k=*)

以及对应的 .pyx 文件

 
cdef class A:

    cdef foo(self):
        print("A")


cdef class B(A):

    cdef foo(self, x=None):
        print("B", x)


cdef class C(B):

    cpdef foo(self, x=True, int k=3):
        print("C", x, k)

注意

这也演示了如何 cpdef 函数可以覆盖 cdef 函数。

结构体中的函数指针

struct 中声明的函数会自动转换为函数指针,以方便使用。

C++ 异常处理

cdef 函数现在可以声明为

cdef int foo(...) except +
cdef int foo(...) except +TypeError
cdef int foo(...) except +python_error_raising_function

在这种情况下,当捕获到 C++ 错误时,将引发 Python 异常。有关更多详细信息,请参阅 在 Cython 中使用 C++

同义词

cdef import fromcdef extern from 的含义相同。

源代码编码

Cython 支持 PEP 3120PEP 263,即您可以使用编码注释开始您的 Cython 源文件,并通常以 UTF-8 编写您的源代码。这会影响字节字符串的编码以及将 unicode 字符串文字(如 u'abcd')转换为 unicode 对象。

自动 typecheck

Cython 不会像 Pyrex 文档 中解释的那样引入新的关键字 typecheck,而是每当使用扩展类型作为第二个参数时,它都会发出一个(不可伪造且更快的)类型检查 isinstance()

来自 __future__ 指令

Cython 支持几个 from __future__ import ... 指令,即 absolute_importunicode_literalsprint_functiondivision

始终启用 with 语句。

纯 Python 模式

Cython 支持编译 .py 文件,并使用装饰器和其他有效的 Python 语法接受类型注释。这允许将相同的源代码解释为纯 Python,或编译以获得优化的结果。有关更多详细信息,请参阅 纯 Python 模式