扩展类型的特殊方法

注意

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

  • Cython 特定的 cdef 语法,旨在使类型声明简洁,并易于从 C/C++ 的角度阅读。

  • 纯 Python 语法,允许在 纯 Python 代码 中使用静态 Cython 类型声明,遵循 PEP-484 类型提示和 PEP 526 变量注释。

    要在 Python 语法中使用 C 数据类型,您需要在要编译的 Python 模块中导入特殊的 cython 模块,例如:

    import cython
    

    如果您使用纯 Python 语法,我们强烈建议您使用最新的 Cython 3 版本,因为与 0.29.x 版本相比,这里已经进行了重大改进。

此页面描述了 Cython 扩展类型当前支持的特殊方法。所有特殊方法的完整列表出现在底部的表格中。其中一些方法的行为与它们的 Python 对应方法不同,或者没有直接的 Python 对应方法,需要特别说明。

注意

所有关于此页面的内容仅适用于扩展类型,使用 cdef 类语句定义或使用 @cclass 装饰器装饰。它不适用于使用 Python class 语句定义的类,在这种情况下,将应用正常的 Python 规则。

声明

扩展类型的特殊方法必须使用 def 声明,而不是 cdef/@cfunc。这不会影响它们的性能——Python 使用不同的调用约定来调用这些特殊方法。

文档字符串

目前,文档字符串在扩展类型的某些特殊方法中不受完全支持。您可以在源代码中放置一个文档字符串作为注释,但它不会在运行时显示在相应的 __doc__ 属性中。(这似乎是 Python 的限制——在 PyTypeObject 数据结构中没有地方可以放置这样的文档字符串。)

初始化方法:__cinit__()__init__()

有两种方法与初始化对象有关,正常的 Python __init__() 方法和特殊的 __cinit__() 方法,可以在其中执行基本的 C 级初始化。

两者之间的主要区别在于它们何时被调用。__cinit__() 方法保证在对象分配期间被调用,但在对象完全初始化之前。具体来说,属于子类或被子类覆盖的方法和对象属性可能尚未完全初始化,并且 __cinit__() 在基类中不能使用它们。请注意,Python 中的对象分配会清除所有字段并将它们设置为零(或 NULL)。Cython 还负责将所有对象属性设置为 None,但同样,这可能尚未对子类定义或覆盖的属性完成。如果您的对象需要比这种基本的属性清除更多的东西才能进入正确和安全的状态,__cinit__() 可能是一个很好的地方来完成它。

另一方面,__init__() 方法的工作方式与 Python 中完全相同。它在对象分配和基本初始化之后被调用,包括完整的继承链。在调用 __init__() 之前,该对象是一个完全有效的 Python 对象,所有操作都是安全的。任何无法在 __cinit__() 方法中安全完成的初始化都应该在 __init__() 方法中完成。但是,与 Python 一样,子类有责任向上调用层次结构,并确保基类中的 __init__() 方法被正确调用。如果子类忘记(或拒绝)调用其一个基类的 __init__() 方法,则该方法将不会被调用。此外,如果对象是通过直接调用其 __new__() 方法 [1](而不是调用类本身)创建的,则不会调用任何 __init__() 方法。

__cinit__() 方法中,您应该执行对象的 C 级基本安全初始化,可能包括分配对象将拥有的任何 C 数据结构。与 __init__() 相反,您的 __cinit__() 方法保证只被调用一次。

如果您的扩展类型具有基类型,则基类型层次结构中任何现有的 __cinit__() 方法将在您的 __cinit__() 方法之前自动调用。您不能显式调用继承的 __cinit__() 方法,基类型可以自由选择是否实现 __cinit__()。如果您需要将修改后的参数列表传递给基类型,则必须在 __init__() 方法中执行初始化的相关部分,在该方法中,调用继承方法的正常规则适用。

传递给构造函数的任何参数都将传递给 __cinit__() 方法和 __init__() 方法。如果您预计会对扩展类型进行子类化,您可能会发现为 __cinit__() 方法提供 *** 参数很有用,这样它就可以接受和忽略任意额外的参数,因为在分配期间通过层次结构传递的参数不能被子类更改。或者,为了方便起见,如果您声明您的 __cinit__() 方法不接受任何参数(除了 self),它将简单地忽略传递给构造函数的任何额外参数,而不会抱怨签名不匹配。

注意

所有构造函数参数都将作为 Python 对象传递。这意味着不可转换的 C 类型(如指针或 C++ 对象)不能传递给构造函数,无论是来自 Python 还是来自 Cython 代码。如果需要,请改用工厂函数或方法来处理对象初始化。直接调用 __new__() 方法通常有助于在该函数中显式绕过对 __init__() 构造函数的调用。

有关示例,请参阅 从现有的 C/C++ 指针实例化

注意

目前,实现 __cinit__() 方法会将类型排除在 自动腌制 之外。

终结方法:__dealloc__()__del__()

__cinit__() 方法的对应方法是 __dealloc__() 方法,它应该执行 __cinit__() 方法的逆操作。在 __cinit__() 方法中显式分配的任何 C 数据(例如,通过 malloc)都应该在 __dealloc__() 方法中释放。

__dealloc__() 方法中,您需要小心操作。当调用 __dealloc__() 方法时,对象可能已经被部分销毁,并且可能处于 Python 认为无效的状态,因此您应该避免调用任何可能触及对象的 Python 操作。特别是,不要调用对象的任何其他方法,也不要执行任何可能导致对象复活的操作。最好只释放 C 数据。

您无需担心释放对象的 Python 属性,因为 Cython 会在 __dealloc__() 方法返回后为您完成此操作。

在子类化扩展类型时,请注意,即使被覆盖,超类的 __dealloc__() 方法也会始终被调用。这与典型的 Python 行为形成对比,在典型的 Python 行为中,除非子类显式调用超类方法,否则不会执行超类方法。

Python 3.4 使扩展类型能够安全地为对象定义终结器。在 Python 3.4 及更高版本上运行 Cython 模块时,您可以向扩展类型添加 __del__() 方法以执行 Python 清理操作。当调用 __del__() 时,对象仍处于有效状态(与 __dealloc__() 的情况不同),允许对它的类成员使用 Python 操作。在 Python <3.4 上,不会调用 __del__()

算术方法

算术运算符方法(例如 __add__())在 Cython 0.x 中的行为与 Python 中的对应方法不同,遵循 C-API 槽函数的底层语义。从 Cython 3.0 开始,它们以与 Python 相同的方式调用,包括这些方法的单独“反转”版本(__radd__() 等)。

以前,如果第一个操作数无法执行操作,则会调用第二个操作数的相同方法,操作数顺序相同。这意味着您不能依赖这些方法的第一个参数是“self”或正确类型,并且您需要在决定要做什么之前测试两个操作数的类型。

如果需要向后兼容性,则可以仍然实现正常的运算符方法(__add__ 等)来支持两种变体,对参数应用类型检查。反转方法(__radd__ 等)始终可以实现为 self 作为第一个参数,并且会被旧的 Cython 版本忽略,而 Cython 3.x 及更高版本只会以预期的参数顺序调用正常方法,否则会调用反转方法。

或者,可以使用指令 c_api_binop_methods=True 来获得旧的 Cython 0.x(或原生 C-API)行为。

如果您无法处理给定的类型组合,则应该返回 NotImplemented。这将使 Python 的运算符实现首先尝试将反转运算符应用于第二个操作数,如果也失败,则向用户报告相应的错误。

这种行为变化也适用于就地算术方法 __ipow__()。它不适用于任何其他就地方法(__iadd__() 等),这些方法始终将 self 作为第一个参数。

丰富比较

实现比较方法有几种方法。根据应用程序的不同,一种方法或另一种方法可能更好。

  • 使用 6 个 Python 特殊方法 __eq__()__lt__() 等。自 Cython 0.27 起支持此功能,并且与普通 Python 类中的工作方式完全相同。

  • 使用单个特殊方法 __richcmp__()。这在一个方法中实现了所有丰富比较操作。签名是 def __richcmp__(self, other, int op)。整数参数 op 指示要执行的操作,如下表所示。

    <

    Py_LT

    ==

    Py_EQ

    >

    Py_GT

    <=

    Py_LE

    !=

    Py_NE

    >=

    Py_GE

    这些常量可以从 cpython.object 模块中 cimport。

  • 如果您在扩展类型/cdef 类上使用 functools.total_ordering 装饰器,Cython 会用专门为扩展类型设计的低级重新实现来替换它。(在普通 Python 类上,functools 装饰器将继续像以前一样工作。)作为快捷方式,您也可以使用 cython.total_ordering,它应用相同的重新实现,但如果类还不是扩展类型,也会将其转换为扩展类型。

import functools
import cython

@functools.total_ordering
@cython.cclass
class ExtGe:
    x: cython.int

    def __ge__(self, other):
        if not isinstance(other, ExtGe):
            return NotImplemented
        return self.x >= cython.cast(ExtGe, other).x

    def __eq__(self, other):
        return isinstance(other, ExtGe) and self.x == cython.cast(ExtGe, other).x

__next__() 方法

希望实现迭代器接口的扩展类型应定义一个名为 __next__() 的方法,而不是 next。Python 系统会自动提供一个 next 方法,该方法会调用您的 __next__()不要显式地为您的类型提供 next() 方法,否则可能会发生不好的事情。

特殊方法表

此表列出了所有特殊方法及其参数和返回类型。在下表中,参数名称 self 用于指示参数具有方法所属的类型。表中未指定类型的其他参数是泛型 Python 对象。

您不必将方法声明为采用这些参数类型。如果您声明不同的类型,将根据需要执行转换。

一般

https://docs.pythonlang.cn/3/reference/datamodel.html#special-method-names

名称

参数

返回类型

描述

__cinit__

self, …

基本初始化(没有直接的 Python 等效项)

__init__

self, …

进一步初始化

__dealloc__

self

基本释放(没有直接的 Python 等效项)

__cmp__

x, y

int

3 路比较(仅限 Python 2)

__str__

self

object

str(self)

__repr__

self

object

repr(self)

__hash__

self

Py_hash_t

哈希函数(返回 32/64 位整数)

__call__

self, …

object

self(…)

__iter__

self

object

返回序列的迭代器

__getattr__

self, name

object

获取属性

__getattribute__

self, name

object

获取属性,无条件

__setattr__

self, name, val

设置属性

__delattr__

self, name

删除属性

丰富比较运算符

https://docs.pythonlang.cn/3/reference/datamodel.html#basic-customization

您可以选择实现标准 Python 特殊方法,如 __eq__(),或实现单个特殊方法 __richcmp__()。根据应用程序的不同,一种方法或另一种方法可能更好。

名称

参数

返回类型

描述

__eq__

self, y

object

self == y

__ne__

self, y

object

self != y (如果不可用,则回退到 __eq__)

__lt__

self, y

object

self < y

__gt__

self, y

object

self > y

__le__

self, y

object

self <= y

__ge__

self, y

object

self >= y

__richcmp__

self, y, int op

object

所有上述方法的组合丰富比较方法(没有直接的 Python 等效项)

算术运算符

https://docs.pythonlang.cn/3/reference/datamodel.html#emulating-numeric-types

名称

参数

返回类型

描述

__add__, __radd__

self, other

object

二元 + 运算符

__sub__, __rsub__

self, other

object

二元 - 运算符

__mul__, __rmul__

self, other

object

* 运算符

__div__, __rdiv__

self, other

object

/ 运算符(用于旧式除法)

__floordiv__, __rfloordiv__

self, other

object

// 运算符

__truediv__, __rtruediv__

self, other

object

/ 运算符(用于新式除法)

__mod__, __rmod__

self, other

object

% 运算符

__divmod__, __rdivmod__

self, other

object

组合除法和取模

__pow__, __rpow__

self, other, [mod]

object

** 运算符或 pow(x, y, [mod])

__neg__

self

object

一元 - 运算符

__pos__

self

object

一元 + 运算符

__abs__

self

object

绝对值

__nonzero__

self

int

转换为布尔值

__invert__

self

object

~ 运算符

__lshift__, __rlshift__

self, other

object

<< 运算符

__rshift__, __rrshift__

self, other

object

>> 运算符

__and__, __rand__

self, other

object

& 运算符

__or__, __ror__

self, other

object

| 运算符

__xor__, __rxor__

self, other

object

^ 运算符

请注意,Cython 0.x 没有使用 __r...__ 变体,而是对常规方法使用双向 C 插槽签名,因此第一个参数是模棱两可的(不是“self”类型)。从 Cython 3.0 开始,运算符调用传递给相应的特殊方法。请参阅上面关于 算术方法 的部分。Cython 0.x 也不支持 __pow____rpow__ 的 2 个参数版本,也不支持 __ipow__ 的 3 个参数版本。

数值转换

https://docs.pythonlang.cn/3/reference/datamodel.html#emulating-numeric-types

名称

参数

返回类型

描述

__int__

self

object

转换为整数

__long__

self

object

转换为长整数

__float__

self

object

转换为浮点数

__oct__

self

object

转换为八进制

__hex__

self

object

转换为十六进制

__index__

self

object

转换为序列索引

就地算术运算符

https://docs.pythonlang.cn/3/reference/datamodel.html#emulating-numeric-types

名称

参数

返回类型

描述

__iadd__

self, x

object

+= 运算符

__isub__

self, x

object

-= 运算符

__imul__

self, x

object

*= 运算符

__idiv__

self, x

object

/= 运算符(用于旧式除法)

__ifloordiv__

self, x

object

//= 运算符

__itruediv__

self, x

object

/= 运算符(用于新式除法)

__imod__

self, x

object

%= 运算符

__ipow__

self, y, [z]

object

**= 运算符(仅在 Python >= 3.8 上支持 3 个参数形式)

__ilshift__

self, x

object

<<= 运算符

__irshift__

self, x

object

>>= 运算符

__iand__

self, x

object

&= 运算符

__ior__

self, x

object

|= 运算符

__ixor__

self, x

object

^= 运算符

序列和映射

https://docs.pythonlang.cn/3/reference/datamodel.html#emulating-container-types

名称

参数

返回类型

描述

__len__

self

Py_ssize_t

len(self)

__getitem__

self, x

object

self[x]

__setitem__

self, x, y

self[x] = y

__delitem__

self, x

del self[x]

__getslice__

self, Py_ssize_t i, Py_ssize_t j

object

self[i:j]

__setslice__

self, Py_ssize_t i, Py_ssize_t j, x

self[i:j] = x

__delslice__

self, Py_ssize_t i, Py_ssize_t j

del self[i:j]

__contains__

self, x

int

x in self

迭代器

https://docs.pythonlang.cn/3/reference/datamodel.html#emulating-container-types

名称

参数

返回类型

描述

__next__

self

object

获取下一个项目(在 Python 中称为 next)

缓冲区接口 [PEP 3118](没有 Python 等效项 - 请参阅注释 1)

名称

参数

返回类型

描述

__getbuffer__

self, Py_buffer *view, int flags

__releasebuffer__

self, Py_buffer *view

自定义类创建

https://docs.pythonlang.cn/3/reference/datamodel.html#customizing-class-creation

名称

参数

返回类型

描述

__set_name__

self, owner, name

在拥有类 owner 创建时自动调用。

描述符对象(参见注释 2)

https://docs.pythonlang.cn/3/reference/datamodel.html#implementing-descriptors

名称

参数

返回类型

描述

__get__

self, instance, class

object

获取属性值

__set__

self, instance, value

设置属性值

__delete__

self, instance

删除属性

注意

(1) 缓冲区接口旨在供 C 代码使用,不能直接从 Python 访问。有关新 API 的操作指南,请参见 实现缓冲区协议。旧的 Python 2 缓冲区协议(`__getreadbuffer__, __getwritebuffer__, __getsegcount__, __getcharbuffer__)从 Cython 3.1 开始不再支持,因为 Python 2 不再支持。

注意

(2) 描述符对象是新式 Python 类支持机制的一部分。请参阅 Python 文档中关于描述符的讨论。另请参见 PEP 252,“使类型看起来更像类”,以及 PEP 253,“内置类型的子类型化”。