Skip to content

扩展类型(又名.cdef 类)

原文: http://docs.cython.org/en/latest/src/tutorial/cdef_classes.html

为了支持面向对象的编程,Cython 支持编写与 Python 完全相同的普通 Python 类:

class MathFunction(object):
    def __init__(self, name, operator):
        self.name = name
        self.operator = operator

    def __call__(self, *operands):
        return self.operator(*operands)

除了支持Python的“内置类型”,Cython还支持第二种类:扩展类型,也叫cdef类(由于用于声明的关键字是cdef)。与 Python 类相比,它们受到一定限制,但通常比通用 Python 类在使用内存上更高效,速度更快。主要区别在于它们使用 C 结构来存储它们的字段和方法而不是 Python 字典。这允许他们在他们的字段中存储任意 C 类型,而不需要 Python 包装器,并且可以直接在 C 级访问字段和方法,而无需通过 Python 字典查找。

普通的 Python 类可以从 cdef 类继承,但是cdef类不可以继承普通Python类。 Cython 需要知道完整的继承层次结构,以便布局它们的 C 结构,并将其限制为单继承。另一方面,普通的 Python 类可以从 Cython 代码和纯 Python 代码中继承任意数量的 Python 类和扩展类型。

到目前为止,我们的积分函数代码并不是非常有用,因为它只集成了一个硬编码函数。为了解决这个问题,我们将使用 cdef 类来表示浮点数上的函数:

cdef class Function:
    cpdef double evaluate(self, double x) except *:
        return 0

指令 cpdef 提供了两种版本的方法;一个快速使用从 Cython 和一个较慢的使用从 Python。然后:

from libc.math cimport sin

cdef class Function:
    cpdef double evaluate(self, double x) except *:
        return 0

cdef class SinOfSquareFunction(Function):
    cpdef double evaluate(self, double x) except *:
        return sin(x ** 2)

这不仅仅是给 cdef 方法提供了一个 Python 包装器:与 cdef 方法不同,cpdef 方法可以被 Python 子类中的方法和实例属性完全覆盖。与 cdef 方法相比,它增加了一点调用开销。

为了使类定义对其他模块可见,从而允许在实现它们的模块之外进行有效的 C 级使用和继承,我们在sin_of_square.pxd文件中定义它们:

cdef class Function:
    cpdef double evaluate(self, double x) except *

cdef class SinOfSquareFunction(Function):
    cpdef double evaluate(self, double x) except *

使用它,我们现在可以更改我们的积分函数代码:

from sin_of_square cimport Function, SinOfSquareFunction

def integrate(Function f, double a, double b, int N):
    cdef int i
    cdef double s, dx
    if f is None:
        raise ValueError("f cannot be None")
    s = 0
    dx = (b - a) / N
    for i in range(N):
        s += f.evaluate(a + i * dx)
    return s * dx

print(integrate(SinOfSquareFunction(), 0, 1, 10000))

这几乎与前面的代码一样快,但是由于可以更改积分函数的算法,因此它更加灵活。我们甚至可以传入 Python 空间中定义的新函数:

>>> import integrate
>>> class MyPolynomial(integrate.Function):
...     def evaluate(self, x):
...         return 2*x*x + 3*x - 10
...
>>> integrate(MyPolynomial(), 0, 1, 10000)
-7.8335833300000077

这比原始的仅使用 Python 的集成代码慢大约 20 倍,但仍然快 10 倍。这显示了当整个循环从 Python 代码移动到 Cython 模块时,很容易获取运行速度的提升。

关于evaluate新实现的一些注意事项:

  • 此处的快速方法派遣机制起作用是因为Function中声明了evaluate。如果在SinOfSquareFunction中引入evaluate,代码仍然可以工作,但 Cython 会使用较慢的 Python 方法派遣机制。
  • 同样的,如果参数f没有被限定对象类型,但只是作为 Python 对象传递,那么将使用较慢的 Python 派遣机制。
  • 由于参数被限定了类型,我们需要检查它是否是None。在 Python 中,当查找evaluate方法时,这会导致AttributeError,但 Cython 会尝试访问None的(不兼容的)内部结构,就像它是Function一样,导致崩溃或数据损坏。

有一个 _ 编译器指令 _ nonecheck,它会以降低速度为代价启用此检查。以下是编译器指令用于动态打开或关闭nonecheck的方法:

# cython: nonecheck=True
#        ^^^ Turns on nonecheck globally

import cython

cdef class MyClass:
    pass

# Turn off nonecheck locally for the function
@cython.nonecheck(False)
def func():
    cdef MyClass obj = None
    try:
        # Turn nonecheck on again for a block
        with cython.nonecheck(True):
            print(obj.myfunc())  # Raises exception
    except AttributeError:
        pass
    print(obj.myfunc())  # Hope for a crash!

cdef 类中的属性与常规类中的属性的行为不同:

  • 所有属性必须在编译时预先声明
  • 属性默认只能从用 Cython 访问(类型化访问)
  • 属性可以声明为暴露在 Python 空间内的动态属性
from sin_of_square cimport Function

cdef class WaveFunction(Function):

    # Not available in Python-space:
    cdef double offset

    # Available in Python-space:
    cdef public double freq

    # Available in Python-space, but only for reading:
    cdef readonly double scale

    # Available in Python-space:
    @property
    def period(self):
        return 1.0 / self.freq

    @period.setter
    def period(self, value):
        self.freq = 1.0 / value



回到顶部