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
是列表、元组或字典,则循环将被优化。您也可以使用 for
… from
语法,但通常建议使用通常的 for
… in
range(...)
语法,并使用 C 运行变量(例如 cdef int i
)。
注意
请参阅 自动范围转换
请注意,Cython 从 Python 2.4 开始也支持集合字面量。
仅限关键字参数¶
Python 函数可以具有仅限关键字的参数,这些参数列在 *
参数之后,并在 **
参数之前(如果有),例如
def f(a, b, *args, c, d = 42, e, **kwds):
...
此处,c
、d
和 e
不能作为位置参数传递,必须作为关键字参数传递。此外,c
和 e
是必需的关键字参数,因为它们没有默认值。
如果 *
后的参数名称被省略,则该函数将不接受任何额外的位置参数,例如
def g(a, b, *, c, d):
...
正好接受两个位置参数,并具有两个必需的关键字参数。
条件表达式“x if b else y”¶
条件表达式,如 https://www.pythonlang.cn/dev/peps/pep-0308/ 中所述
X if C else Y
仅评估 X
和 Y
之一(取决于 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
布尔 int 类型(例如,它像 c int 一样工作,但强制转换为/从 python 转换为布尔值)¶
在 C 中,int 用于真值。在 python 中,任何对象都可以用作真值(使用 __nonzero__()
方法),但规范的选择是两个布尔对象 True
和 False
。 bint
(表示“布尔 int”)类型编译为 C int,但强制转换为和从 Python 转换为布尔值。比较和几个内置函数的返回值也是 bint
。这减少了将事物包装在 bool()
中的需要。例如,可以编写
def is_equal(x):
return x == y
这将在 Pyrex 中返回 1
或 0
,但在 Cython 中返回 True
或 False
。可以将变量和函数的返回值声明为 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 在通常的 def
和 cdef
之上添加了第三种函数类型。如果函数被声明为 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 现在支持 cdef
和 cpdef
函数的可选参数。
在 .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)
结构体中的函数指针¶
在 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 from
与 cdef extern from
的含义相同。
源代码编码¶
Cython 支持 PEP 3120 和 PEP 263,即您可以使用编码注释开始您的 Cython 源文件,并通常以 UTF-8 编写您的源代码。这会影响字节字符串的编码以及将 unicode 字符串文字(如 u'abcd'
)转换为 unicode 对象。
自动 typecheck
¶
Cython 不会像 Pyrex 文档 中解释的那样引入新的关键字 typecheck
,而是每当使用扩展类型作为第二个参数时,它都会发出一个(不可伪造且更快的)类型检查 isinstance()
。
来自 __future__ 指令¶
Cython 支持几个 from __future__ import ...
指令,即 absolute_import
、unicode_literals
、print_function
和 division
。
始终启用 with 语句。
纯 Python 模式¶
Cython 支持编译 .py
文件,并使用装饰器和其他有效的 Python 语法接受类型注释。这允许将相同的源代码解释为纯 Python,或编译以获得优化的结果。有关更多详细信息,请参阅 纯 Python 模式。