扩展类型的特殊方法¶
注意
此页面使用两种不同的语法变体
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
import functools
@functools.total_ordering
cdef class ExtGe:
cdef int x
def __ge__(self, other):
if not isinstance(other, ExtGe):
return NotImplemented
return self.x >= (<ExtGe>other).x
def __eq__(self, other):
return isinstance(other, ExtGe) and self.x == (<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 (如果不可用,则回退到 |
__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 不再支持。