通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表达力的代码——或者说,更具 Python 风格的代码。
| 功能 | 协议接口 |
|---|---|
| + | __add__ |
| * | __mul__ |
| str() | 先查找是否实现 __str__ 协议,没有查找是否实现 __repr__ |
| bool() | 默认情况下,我们自己定义的类的实例总被认为是真的,除非这个类对 __bool__ 或者 __len__ 函数有自己的实现。bool(x) 的背后是调用 x.__bool__() 的结果;如果不存在 __bool__ 方法,那么 bool(x) 会尝试调用 x.__len__()。若返回 0,则 bool 会返回 False;否则返回 True |
| 字符串 / 字节序列 表示形式 | __repr__、__str__、__format__、__bytes__ |
| 数值转换 | __abs__、__bool__、__complex__、__int__、__float__、__hash__、__index__ |
| 集合模拟 | __len__、__getitem__、__setitem__、__delitem__、__contains__ |
| 迭代枚举 | __iter__、__reversed__、__next__ |
| 可调用模拟 | __call__ |
| 上下文管理 | __enter__、__exit__ |
| 实例创建和销毁 | __new__、__init__、__del__ |
| 属性管理 | __getattr__、__getattribute__、__setattr__、__delattr__、__dir__ |
| 属性描述符 | __get__、__set__、__delete__ |
| 跟类相关的服务 | __prepare__、__instancecheck__、__subclasscheck__ |
| 一元运算符 | __neg__ -、__pos__ +、__abs__ abs() |
| 众多比较运算符 | __lt__ <、__le__ <=、__eq__ ==、__ne__ !=、__gt__ >、__ge__ >= |
| 算术运算符 | __add__ +、__sub__ -、__mul__ *、__truediv__ /、__floordiv__ //、__mod__ %、__divmod__ divmod()、__pow__ ** 或 pow()、__round__ round() |
| 反向算术运算符 | __radd__、__rsub__、__rmul__、__rtruediv__、__rfloordiv__、__rmod__、__rdivmod__、__rpow__ |
| 增量赋值算术运算符 | __iadd__、__isub__、__imul__、__itruediv__、__ifloordiv__、__imod__、__ipow__ |
| 位运算符 | __invert__ ~、__lshift__ <<、__rshift__ >>、__and__ &、__or__ |、__xor__ ^ |
| 反向位运算符 | __rlshift__、__rrshift__、__rand__、__rxor__、__ror__ |
| 增量赋值位运算符 | __ilshift__、__irshift__、__iand__、__ixor__、__ior__ |
更具体细致的表格总结如下
1.基础知识


- 对
__init__()方法的调用发生在实例被创建 之后 。如果要控制实际创建进程,请使用__new__()方法 - 按照约定,
__repr__()方法所返回的字符串为合法的 Python表达式。 - 在调用
print(x)的同时也调用了__str__()方法。 - 由于
bytes类型的引入而从Python 3开始出现 - 按照约定,format_spec 应当遵循
迷你语言格式规范Python 标准类库中的decimal.py提供了自己的__format__()方法。
2.行为方式与迭代器类似的类

- 无论何时创建
迭代器都将调用__iter__()方法。这是用初始值对迭代器进行初始化的绝佳之处。 - 无论何时从迭代器中获取下一个值都将调用
__next__()方法。 __reversed__()方法并不常用。它以一个现有序列为参数,并将该序列中所有元素从尾到头以逆序排列生成一个新的迭代器
3.计算属性


- 如果某个类定义了
__getattribute__()方法,在 每次引用属性或方法名称时Python都调用它(特殊方法名称除外,因为那样将会导致讨厌的无限循环) - 如果某个类定义了
__getattr__()方法,Python将只在正常的位置查询属性时才会调用它。如果实例x定义了属性color,x.color将 不会 调用x.__getattr__('color');而只会返回x.color已定义好的值。 - 无论何时给
属性赋值,都会调用__setattr__()方法。 - 无论何时
删除一个属性,都将调用__delattr__()方法。 - 如果定义了
__getattr__()或__getattribute__()方法,__dir__()方法将非常有用。通常,调用dir(x)将只显示正常的属性和方法。如果__getattr()__方法动态处理color属性,dir(x)将不会将color列为可用属性。可通过覆盖__dir__()方法允许将color列为可用属性,对于想使用你的类但却不想深入其内部的人来说,该方法非常有益。
__getattr__() 和 __getattribute__() 方法的区别非常细微,但非常重要。可以用两个例子来解释一下
# -*- coding: utf-8 -*-
class Dynamo:
# 属性名称以字符串的形式传入 __getattr()__ 方法。如果名
# 称为 'color',该方法返回一个值。(在此情况下,它只是一个
# 硬编码的字符串,但可以正常地进行某些计算并返回结果。)
def __getattr__(self, key):
if key == 'color':
return 'PapayaWhip'
else:
# 如果属性名称未知, __getattr()__ 方法必须引发一个
# AttributeError 例外,否则在访问未定义属性时,代码将只会
# 默默地失败。(从技术角度而言,如果方法不引发例外或显式
# 地返回一个值,它将返回 None ——Python 的空值。这意味着 所
# 有 未显式定义的属性将为 None,几乎可以肯定这不是你想看到
# 的。)
raise AttributeError
if __name__ == '__main__':
dyn = Dynamo()
# dyn 实例没有名为 color 的属性,因此在提供计算值时将调用
# __getattr__() 。
print(dyn.color)
# PapayaWhip
dyn.color = 'LemonChiffon'
# 在显式地设置 dyn.color 之后,将不再为提供 dyn.color 的
# 值而调用 __getattr__() 方法,因为 dyn.color 已在该实例中定
# 义。
print(dyn.color)
# LemonChiffon
另一方面,__getattribute__() 方法是绝对的、无条件的。
# -*- coding: utf-8 -*-
class SuperDynamo:
def __getattribute__(self, key):
if key == 'color':
return 'PapayaWhip'
else:
raise AttributeError
if __name__ == '__main__':
dyn = SuperDynamo()
# 在获取 dyn.color 的值时将调用 __getattribute__() 方法
print(dyn.color)
# PapayaWhip
dyn.color = 'LemonChiffon'
# 即便已经显式地设置 dyn.color,在获取 dyn.color 的值时,
# 仍将调用 __getattribute__() 方法。如果存在
# __getattribute__() 方法,将在每次查找属性和方法时 无条件
# 地调用 它,哪怕在创建实例之后已经显式地设置了属性。
print(dyn.color)
# PapayaWhip
tips:
如果定义了类的 __getattribute__()方法,你可能还想定义一个 __setattr__()方法,并在两者之间进行协同,以跟踪属
性的值。否则,在创建实例之后所设置的值将会消失在黑洞中。
必须特别小心 __getattribute__() 方法,因为 Python 在查找类的方法名称时也将对其进行调用。
# -*- coding: utf-8 -*-
class Rastan:
def __getattribute__(self, key):
# 该类定义了一个总是引发 AttributeError 例外的
# __getattribute__() 方法。没有属性或方法的查询会成功。
raise AttributeError
def swim(self):
return "swim"
if __name__ == '__main__':
hero = Rastan()
# 调用 hero.swim() 时,Python 将在 Rastan 类中查找 swim()
# 方法。该查找将执行整个 __getattribute__() 方法,因为所有
# 的属性和方法查找都通过 __getattribute__() 方法。在此例
# 中, __getattribute__() 方法引发 AttributeError 例外,因此
# 该方法查找过程将会失败,而方法调用也将失败。
hero.swim()
# Traceback (most recent call last):
# File "C:/myFiles/company_project/xbot/my_test/getattribute_test.py", line 22, in <module>
# hero.swim()
# File "C:/myFiles/company_project/xbot/my_test/getattribute_test.py", line 9, in __getattribute__
# raise AttributeError
# AttributeError
4.行为方式与函数类似的类
可以让类的实例变得可调用——就像函数可以调用一样——通过定义 __call__() 方法。

5.行为方式与序列类似的类
如果类作为一系列值的容器出现——也就是说如果对某个类来说,是否“包含”某值是件有意义的事情——那么它也许应该定义
下面的特殊方法已,让它的行为方式与序列类似。

6.行为方式与字典类似的类

7.行为方式与数值类似的类
使用适当的特殊方法,可以将类的行为方式定义为与数字相仿。也就是说,可以进行相加、相减,并进行其它数学运算。


之前提到的特殊方法集合采用了第一种方式:对于给定 x / y,它们为 x 提供了一种途径来表述“我知道如何将自己除以 y。”下
面的特殊方法集合采用了第二种方法:它们向 y 提供了一种途径来表述“我知道如何成为分母,并用自己去除 x。


对于复合运算(原地操作符)


注意:多数情况下,并不需要原地操作方法。如果未对特定运算定义“就地”方法,Python 将会试着使用(普通)方法。例
如,为执行表达式 x /= y,Python 将会:
- 试着调用
x.__itruediv__(y)。如果该方法已经定义,并返回了NotImplemented之外的值,那已经大功告成了。 - 试图调用
x.__truediv__(y)。如果该方法已定义并返回一个NotImplemented之外的值,x的旧值将被丢弃,并将所返回的
值替代它,就像是进行了x = x / y运算。
试图调用y.__rtruediv__(x)。如果该方法已定义并返回了一个NotImplemented之外的值,x的旧值将被丢弃,并用所返回值进行替换
因此如果想对原地运算进行优化,仅需像 __itruediv__() 方法一样定义“原地”方法。否则,基本上 Python 将会重新生成原地
运算公式,以使用常规的运算及变量赋值。
还有一些“一元”数学运算,可以对“类‐数字”对象自己执行


8.可比较的类
如果要创建自己的类,且对象之间的比较有意义,可以使用下面的特殊方法来实现比较。

tips:
如果定义了 __lt__() 方法但没有定义__gt__()方法,Python 将通过经交换的算子调用 __lt__() 方法。然而,Python 并不
会组合方法。例如,如果定义了 __lt__()方法和 __eq()__ 方法,并试图测试是否 x<= y,Python 不会按顺序调用 __lt__() 和__eq()__ 。它将只调用 __le__() 方法。
9.可序列化的类
Python 支持 任意对象的序列化和反序列化。(多数 Python 参考资料称该过程为 “pickling” 和 “unpickling”)。该技术对与将状态保存为文件并在稍后恢复它非常有意义。所有的 内置数据类型 均已支持 pickling 。如果创建了自定义类,且希望它能够pickle,阅读 pickle 协议 了解下列特殊方法何时以及如何被调用。


要重建序列化对象,Python 需要创建一个和被序列化的对象看起来一样的新对象,然后设置新对象的所有属性。__getnewargs__() 方法控制新对象的创建过程,而__setstate__() 方法控制属性值的还原方式。
10.可在WITH语块中使用的类
with 语块定义了 运行时刻上下文环境;在执行 with 语句时将“进入”该上下文环境,而执行该语块中的最后一条语句将“退出”
该上下文环境。

该 __exit__() 方法将总是被调用,哪怕是在 with 语块中引发了例外。实际上,如果引发了例外,该例外信息将会被传递给 __exit__() 方法。查阅 With 状态上下文环境管理器 了解更多细节。
11.真正神奇的东西
如果知道自己在干什么,你几乎可以完全控制类是如何比较的、属性如何定义,以及类的子类是何种类型。


tips: 确切掌握 Python 何时调用 __del__() 特别方法 是件难以置信的复杂事情。要想完全理解它,必须清楚 Python 如何在内存中跟踪对象。了解 Python 垃圾收集和类析构器。还可以阅读 《弱引用》、《weakref 模块》,还可以将 《gc模块》 当作补充阅读材料。
本文介绍了Python中如何通过实现特殊方法来定制数据类型,以及这些方法如__str__、__bool__、__getattribute__等在类的行为控制中的作用,包括实例创建、比较、序列化和上下文管理等内容。
428

被折叠的 条评论
为什么被折叠?



