为了速度的早期绑定¶
注意
此页面使用两种不同的语法变体
Cython 特定的
cdef
语法,旨在使类型声明简洁且易于从 C/C++ 的角度阅读。纯 Python 语法,允许在 纯 Python 代码 中使用静态 Cython 类型声明,遵循 PEP-484 类型提示和 PEP 526 变量注释。
要在 Python 语法中使用 C 数据类型,您需要在要编译的 Python 模块中导入特殊的
cython
模块,例如import cython
如果您使用纯 Python 语法,我们强烈建议您使用最新的 Cython 3 版本,因为与 0.29.x 版本相比,这里已经进行了重大改进。
作为一种动态语言,Python 鼓励一种编程风格,即从方法和属性的角度考虑类和对象,而不是它们在类层次结构中的位置。
这使得 Python 成为一种非常轻松舒适的语言,适合快速开发,但也有一个代价 - 管理数据类型的“繁文缛节”被转嫁给了解释器。在运行时,解释器会做很多工作来搜索命名空间、获取属性和解析参数和关键字元组。这种运行时的“后期绑定”是 Python 相对于“早期绑定”语言(如 C++)速度相对较慢的主要原因。
但是,使用 Cython,可以通过使用“早期绑定”编程技术来获得显著的加速。
例如,考虑以下(愚蠢的)代码示例
@cython.cclass
class Rectangle:
x0: cython.int
y0: cython.int
x1: cython.int
y1: cython.int
def __init__(self, x0: cython.int, y0: cython.int, x1: cython.int, y1: cython.int):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
def area(self):
area = (self.x1 - self.x0) * (self.y1 - self.y0)
if area < 0:
area = -area
return area
def rectArea(x0, y0, x1, y1):
rect = Rectangle(x0, y0, x1, y1)
return rect.area()
cdef class Rectangle:
cdef int x0, y0
cdef int x1, y1
def __init__(self, int x0, int y0, int x1, int y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
def area(self):
area = (self.x1 - self.x0) * (self.y1 - self.y0)
if area < 0:
area = -area
return area
def rectArea(x0, y0, x1, y1):
rect = Rectangle(x0, y0, x1, y1)
return rect.area()
在 rectArea()
方法中,对 rect.area()
和 area()
方法的调用包含大量 Python 开销。
但是,在 Cython 中,可以在 Cython 代码中进行调用时消除大量此类开销。例如
@cython.cclass
class Rectangle:
x0: cython.int
y0: cython.int
x1: cython.int
y1: cython.int
def __init__(self, x0: cython.int, y0: cython.int, x1: cython.int, y1: cython.int):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
@cython.cfunc
def _area(self) -> cython.int:
area: cython.int = (self.x1 - self.x0) * (self.y1 - self.y0)
if area < 0:
area = -area
return area
def area(self):
return self._area()
def rectArea(x0, y0, x1, y1):
rect: Rectangle = Rectangle(x0, y0, x1, y1)
return rect._area()
cdef class Rectangle:
cdef int x0, y0
cdef int x1, y1
def __init__(self, int x0, int y0, int x1, int y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
cdef int _area(self):
cdef int area = (self.x1 - self.x0) * (self.y1 - self.y0)
if area < 0:
area = -area
return area
def area(self):
return self._area()
def rectArea(x0, y0, x1, y1):
cdef Rectangle rect = Rectangle(x0, y0, x1, y1)
return rect._area()
在这里,在 Rectangle 扩展类中,我们定义了两种不同的面积计算方法,高效的 _area()
C 方法,以及 Python 可调用的 area()
方法,它充当 _area()
的一个薄包装器。还要注意在函数 rectArea()
中,我们如何通过声明局部变量 rect
来“早期绑定”,该变量被明确地赋予了 Rectangle 类型。通过使用此声明,而不是仅仅动态地分配给 rect
,我们获得了访问更高效的 C 可调用 _area()
方法的能力。
但是 Cython 为我们提供了更多简单性,它允许我们声明双重访问方法 - 可以在 C 级高效调用,但也可以从纯 Python 代码中访问的方法,代价是 Python 访问开销。考虑以下代码
@cython.cclass
class Rectangle:
x0: cython.int
y0: cython.int
x1: cython.int
y1: cython.int
def __init__(self, x0: cython.int, y0: cython.int, x1: cython.int, y1: cython.int):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
@cython.ccall
def area(self)-> cython.int:
area: cython.int = (self.x1 - self.x0) * (self.y1 - self.y0)
if area < 0:
area = -area
return area
def rectArea(x0, y0, x1, y1):
rect: Rectangle = Rectangle(x0, y0, x1, y1)
return rect.area()
cdef class Rectangle:
cdef int x0, y0
cdef int x1, y1
def __init__(self, int x0, int y0, int x1, int y1):
self.x0 = x0
self.y0 = y0
self.x1 = x1
self.y1 = y1
cpdef int area(self):
cdef int area = (self.x1 - self.x0) * (self.y1 - self.y0)
if area < 0:
area = -area
return area
def rectArea(x0, y0, x1, y1):
cdef Rectangle rect = Rectangle(x0, y0, x1, y1)
return rect.area()
在这里,我们只有一个面积方法,声明为 cpdef
或使用 @ccall
装饰器,使其可以高效地作为 C 函数调用,但仍然可以从纯 Python(或后期绑定 Cython)代码中访问。
如果在 Cython 代码中,我们已经有一个“早期绑定”的变量(即,明确声明为 Rectangle 类型,(或强制转换为 Rectangle 类型),那么调用其面积方法将使用高效的 C 代码路径并跳过 Python 开销。但是,如果在 Cython 或常规 Python 代码中,我们有一个存储 Rectangle 对象的常规对象变量,那么调用面积方法将需要
对面积方法进行属性查找
为参数打包元组,为关键字打包字典(在本例中均为空)
使用 Python API 调用方法
以及在 area 方法本身内部
解析元组和关键字
执行计算代码
将结果转换为 Python 对象并返回
因此,在 Cython 中,可以通过在变量声明和转换中使用强类型来实现巨大的优化。对于使用方法调用的紧密循环,以及这些方法是纯 C 的情况,差异可能是巨大的。