为了速度的早期绑定

注意

此页面使用两种不同的语法变体

  • 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()

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()

在这里,在 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()

在这里,我们只有一个面积方法,声明为 cpdef 或使用 @ccall 装饰器,使其可以高效地作为 C 函数调用,但仍然可以从纯 Python(或后期绑定 Cython)代码中访问。

如果在 Cython 代码中,我们已经有一个“早期绑定”的变量(即,明确声明为 Rectangle 类型,(或强制转换为 Rectangle 类型),那么调用其面积方法将使用高效的 C 代码路径并跳过 Python 开销。但是,如果在 Cython 或常规 Python 代码中,我们有一个存储 Rectangle 对象的常规对象变量,那么调用面积方法将需要

  • 对面积方法进行属性查找

  • 为参数打包元组,为关键字打包字典(在本例中均为空)

  • 使用 Python API 调用方法

以及在 area 方法本身内部

  • 解析元组和关键字

  • 执行计算代码

  • 将结果转换为 Python 对象并返回

因此,在 Cython 中,可以通过在变量声明和转换中使用强类型来实现巨大的优化。对于使用方法调用的紧密循环,以及这些方法是纯 C 的情况,差异可能是巨大的。