3.3.10. 特殊方法查找
对于自定义类来说,特殊方法的隐式发起调用仅保证在其定义于对象类型中时能正确地发挥作用,而不能定义在对象实例字典中。 该行为就是以下代码会引发异常的原因。:
>>> class C:
... pass
...
>>> c = C()
>>> c.__len__ = lambda: 5
>>> len(c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'C' has no len()
此行为背后的原理在于包括类型对象在内的所有对象都会实现的几个特殊方法,例如 __hash__()
和 __repr__()
。 如果这些方法的隐式查找使用了传统的查找过程,它们会在对类型对象本身发起调用时失败:
>>> 1 .__hash__() == hash(1)
True
>>> int.__hash__() == hash(int)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' of 'int' object needs an argument
以这种方式不正确地尝试发起调用一个类的未绑定方法有时被称为‘元类混淆’,可以通过在查找特殊方法时绕过实例的方式来避免:
>>> type(1).__hash__(1) == hash(1)
True
>>> type(int).__hash__(int) == hash(int)
True
除了为了正确性而绕过任何实例属性之外,隐式特殊方法查找通常也会绕过 __getattribute__()
方法,甚至包括对象的元类:
>>> class Meta(type):
... def __getattribute__(*args):
... print("Metaclass getattribute invoked")
... return type.__getattribute__(*args)
...
>>> class C(object, metaclass=Meta):
... def __len__(self):
... return 10
... def __getattribute__(*args):
... print("Class getattribute invoked")
... return object.__getattribute__(*args)
...
>>> c = C()
>>> c.__len__() # Explicit lookup via instance
Class getattribute invoked
10
>>> type(c).__len__(c) # Explicit lookup via type
Metaclass getattribute invoked
10
>>> len(c) # Implicit lookup
10
以这种方式绕过 __getattribute__()
机制为解析器内部的速度优化提供了显著的空间,其代价则是牺牲了处理特殊方法时的一些灵活性(特殊方法 必须 设置在类对象本身上以便始终一致地由解释器发起调用)。