Skip to content

扩展类型的特殊方法

原文: http://docs.cython.org/en/latest/src/userguide/special_methods.html

本页介绍了 Cython 扩展类型当前支持的特殊方法。所有特殊方法的完整列表显示在底部的表格中。其中一些方法的行为与 Python 对应方式不同,或者没有直接的 Python 对应方式,需要特别注意。

注意: 此页面上说的所有内容仅适用于使用cdef class语句或者@cclass装饰器定义的扩展类型。它不适用于使用 Python class 语句定义的类 -- 适用普通的 Python 规则。

声明

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

文档字符串

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

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

初始化对象有两种方法。一个普通的Python的 __init__() 方法和一个特殊的完成基本C级别初始化的 __cinit__() 方法。

这两者的主要区别在于它们被调用的时机。__cinit__() 作为对象分配的一部分保证会被调用(但是在对象完全初始化之前)。意思是,属于子类或者是被子类覆盖的方法和对象属性在此时还没有初始化完毕,因此不可以在基类中被 __cinit__() 方法使用。注意,Python中的对象分配会清空所有属性并设置为零(或者NULL)。Cython除此之外会将所有对象属性设置为 None,但是重复一遍,此时属于子类或者是被子类覆盖的方法和对象属性在此时还没有初始化完毕。如果你需要在基本属性清空的时候保证它进入正确、安全的状态,那也许可以在 __cinit__ 完成这件事。

__init__() 方法工作方式和Python中完全一样。它会在完成对象分配和基础初始化以及完整的继承链之后调用。在 __init__() 被调用的时候,对象已经完全是一个有效的Python对象了,因此所有的操作都是安全的。任何在 __cinit__() 不能安全的完成的操作都应该在 __init__() 中完成。但是在Python中,需要子类来调用继承树来保证父类的 __init__() 方法被调用。如果子类忘记或者拒绝调用它其中一个父类的 __init__() 方法,这个方法就不会被调用。除此之外,如果一个对象是被直接调用它的 __new__() 方法创建的(而不是调用类本身),那么任何 __init__() 方法都不会被调用。

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

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

所有传递给构造器的参数都会同时传递给 __cinit__() 方法和 __init__() 方法。如果你想继承你的扩展类型,你也行该给 __cinit__() 方法设置 *** 参数以便让它能够接收任意多的额外参数,因此在对象分配时继承树中的传递参数是不能被子类改变的。或者,为了更方便,如果你声明你的 __cinit__() 不接收任何参数(除了它自己),那么它会忽视传递给构造器的额外参数而不会因为签名不匹配而发出警告.

  • 注意:所有构造函数参数都将作为 Python 对象传递。这意味着不可转换的 C 类型(如指针或 C ++对象)无法从 Cython 代码传递到构造函数中。如果需要,请使用工厂函数来处理对象初始化。通常可以直接调用此函数中的__new__()以绕过对__init__()构造函数的调用。
  • 注意:实现 __cinit__() 方法将禁止该类型使用自动序列化。

完成方法(Finalize):__dealloc__()

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

您需要对在 __dealloc__() 方法中执行的操作保持谨慎。在调用 __dealloc__() 方法时,对象可能已经被部分破坏,并且就 Python 而言可能不处于有效状态,因此您应该避免调用可能触及该对象的任何 Python 操作。特别是,不要调用对象的任何其他方法或做任何可能导致对象复活的事情。你最好只是释放C 数据。

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

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

Python3.4允许扩展类型安全得定义对象的Finalizer。在Python3.4或者更高版本上运行Cython模块的时候,你可以给扩展类型增加一个 __del__() 方法以完成Python得清理操作。当 __del__() 被调用的时候对象仍然处于一个有效的状态(和 __dealloc__() 的状况不同),因此允许使用它类型成员上的操作。在低于Python3.4的版本中,__del__()方法不会被调用

算术方法

在Cython0.X的版本中,算术运算符方法(如 __add__() )的行为遵循底层对C-API 插槽函数的语义,与 Python 对应方法不同这些。自Cython3.0版本起,它们的调用方式和在Python中一样,包括这些函数的单独的"反向"版本(比如:__add()____radd__())。

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

如果需要向后兼容性,普通的运算方法(比如 __add__ 之类的)仍然可以被实现为支持两种变体的形式,添加一个参数的类型检查即可。反过来的方法(比如 __radd()__)总是可以被实现为第一个参数为 self且在更旧版本的Cython中会被忽略掉,而Cython3.x及以后的版本中,会根据预期的参数顺序调用正常的方法,如果顺序反了就调用反向的方法。

或者,Cython旧版本0.X(以及原生C-API)的行为可以通过指令 c_api_binop_methods=True 来设置。

如果你不能处理你获取的类型组合,你应该返回 NotImplemented。这会让Python的运算符实现首先尝试第二个操作数的反向方法,如果那个也失败了,则向用户报告错误。

这也适用于原位执行的算术方法 __ipow__() 。它不适用于任何其他始终将 self 作为第一个参数的原位方法( __iadd__() 等)。

富比较

实现比较方法有几种办法。根据应用程序的不同,每种方法的优劣不同:

  • 使用 6 个 Python的 特殊方法 __eq__()__lt__()等。这是 Cython 0.27 以来的新功能,与普通 Python 类完全一样。

  • 第二种方法使用单一特殊方法 __richcmp__() 。这在一个方法中实现了所有富比较操作。签名是 def __richcmp__(self, other, int op) 。整数参数 op 表示要执行的操作,如下表所示: | 运算符 | op | | --- | --- | | < | Py_LT | | == | Py_EQ | | > | Py_GT | | <= | Py_LE | | != | Py_NE | | >= | Py_GE |

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

  • 如果你在一个扩展类型或者 cdef 类上 使用functools.total_ordering<https://docs.python.org/3/library/functools.html#functools.total_ordering>装饰器,Cython会用这个扩展类型专属的底层实现来替换。(在普通的Python类上,functools装饰器像以前一样有效)。你还可以使用 cython.total_ordering 作为捷径,这也会产生同样的重新实现的效果。不仅如此,如果这个类还不是扩展类型,还会将其转化为一个扩展类型。

# 经典风格
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

# 纯python风格
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.python.org/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.python.org/3/reference/datamodel.html#basic-customization

您可以选择实现标准的 Python 特殊方法,如 __eq__() 或单个特殊方法 __richcmp__() 。根据应用,优劣不同。

Name Parameters Return type Description
__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 等价物)

译者注:可以这么记这些方法。这里的e表示equal,l表示less,g表示greater,t表示than。因此lt=less than,le表示less or equal (to)。

算术运算符

https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types

Name Parameters Return type Description
__add__, __radd__ self, 另一个 object 二元 + 运算符
__sub__, __rsub__ self, 另一个 object 二元 - 运算符
__mul__, __rmul__ self, 另一个 object * 运算符
__div__, __rdiv__ self, 另一个 object / 运算符用于旧式除法
__floordiv__, __rfloordiv__ self, 另一个 object // 运算符
__truediv__, __rtruediv__ self, 另一个 object / 运算符用于新式除法
__mod__, __rmod__ self, 另一个 object % 运算符
__divmod__, __rdivmod__ self, 另一个 object 组合 div 和 mod
__pow__, __rpow__ self, 另一个, [余数] object ** 运算符或 pow(x,y,z)
__neg__ self object 一元 - 运算符
__pos__ self object 一元 + 运算符
__abs__ self object 绝对值
__nonzero__ self int 转换为布尔值
__invert__ self object ~ 运算符
__lshift__, __rlshift__ self, 另一个 object << 运算符
__rshift__, __rrshift__ self, 另一个 object >> 运算符
__and__, __rand__ self, 另一个 object & 运算符
__or__, __ror__ self, 另一个 object | 运算符
__xor__, __rxor__ self, 另一个 object ^ 运算符

注意:Cython0.X版本并不使用 __r...__ 这些变体,而是使用常规方法的双向的C插槽签名,因此第一个参数无法确定(不一定是 self 类型)。从Cython3.0起,运算符调用总是传递给对应的特殊方法。查看上面的算术运算符的表格来查看对应的关系。Cython0.X还不支持2个参数版本的 __pow____rpow__ 或者 三个参数版本的 __ipow__

数字转换

https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types

Name Parameters Return type Description
__int__ self object 转换为整数
__long__ self object 转换为长整数
__float__ self object 转换为浮动
__oct__ self object 转换为八进制
__hex__ self object 转换为十六进制
__index__ self object 转换为序列索引

原位算术运算符

https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types

Name Parameters Return type Description
__iadd__ self,x object += 算符
__isub__ self, x object -= 运算符
__imul__ self, x object *= 运算符
__idiv__ self, x object /= 运算符用于旧式除法
__ifloordiv__ self, x object //= 运算符
__truediv__ self, x object /= 运算符用于新式除法
__imod__ self, x object %= 运算符
__ipow__ self, y, [z] object **= 运算符(3个参数的形式仅在Python3.8+支持)
__ilshift__ self, x object <<= 运算符
__irshift__ self, x object >>= 运算符
__iand__ self, x object &= 运算符
__ior__ self, x object |= 运算符
__ixor__ self, x object ^= 运算符

序列和映射

https://docs.python.org/3/reference/datamodel.html#emulating-container-types

Name Parameters Return type Description
__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.python.org/3/reference/datamodel.html#emulating-container-types

Name Parameters Return type Description
__next__ self object 获取下一项(在 Python 中称为 next)

缓冲接口[ PEP 3118 ](没有 Python 当量 - 见注 1)

Name Parameters Return type Description
__getbuffer__ self,Py_buffer *view, int flags    
__releasebuffer__ self,Py_buffer *view    

缓冲接口[遗留](没有 Python 对应 - 见注 1)

Name Parameters Return type Description
__getreadbuffer__ self,Py_ssize_t i,void *p    
__getwritebuffer__ self, Py_ssize_t i, void **p    
__getsegcount__ self,Py_ssize_t *p    
__getcharbuffer__ self,Py_ssize_t i,char **p    

描述符对象(见注 2)

https://docs.python.org/3/reference/datamodel.html#emulating-container-types

Name Parameters Return type Description
__get__ self,instance,class object 获取属性的值
__set__ self,instance,value   设置属性值
__delete__ self,instance   Delete attribute
  • 注(1) 缓冲区接口旨在供 C 代码使用,不能直接从 Python 访问。它在 Python 2.x 的 Python / C API 参考手册的 6.6 和 10.6 节中描述。它被 Python 2.6 中新的 PEP 3118 缓冲协议取代,并且在 Python 3 中不再可用。有关新 API 的操作指南,见 实现缓冲协议

  • 注(2) 描述符对象是新式 Python 类的支持机制的一部分。请参阅 Python 文档中对描述符的讨论。另见 PEP 252 ,“使类型看起来更像类”, PEP 253 ,“Subtyping Built-In Types”。



回到顶部