1 对特殊方法的访问 - Special method lookup 2 3 对于用户自定义的 class 来说, 特殊方法只有通过定义对象的类型object’s type (而非通过 instance 4 的 __dict__属性)被定义, 才能保证特殊方法的隐式调用. 5 也就是说给自定义的 class 打 特殊方法的 monkey patching 后,再通过 conventional'传统方式' 调用是行不通的. 6 注, conventional'传统方式' 调用 指的是如下面例子中 len() 跟 __len__() 的对应关系. 7 见下例的 exception, 8 例, 9 >>> class C: 10 ... pass 11 ... 12 >>> c = C() 13 >>> c.__len__ = lambda: 5 14 >>> c.__len__() 15 5 16 >>> len(c) 17 Traceback (most recent call last): 18 File "<stdin>", line 1, in <module> 19 TypeError: object of type 'C' has no len() 20 21 报错的原因在于, 所有对象(包括类型对象) 都有实现特殊方法, 如果通过'传统方式'隐式调用 22 这些特殊方法(len(c)), 当在类型对象本身上调用对应的特殊方法的时候便会失败. 23 详见 python doc 中的描述, 24 The rationale behind this behaviour lies with a number of special methods such as __hash__() and __repr__() 25 that are implemented by all objects, including type objects. 26 If the implicit lookup of these methods used the conventional lookup process, 27 they would fail when invoked on the type object itself: 28 29 例如, 30 >>> 1 .__hash__() == hash(1) 31 True 32 >>> int.__hash__() == hash(int) 33 Traceback (most recent call last): 34 File "<stdin>", line 1, in <module> 35 TypeError: descriptor '__hash__' of 'int' object needs an argument 36 37 错误在于, 试图去调用一个类(int) 的 unbound method(__hash__), 有时这样的错误调用行为 38 被称作‘metaclass confusion’. 可以采取通过 instance 访问 special methods 39 的方式绕过 ‘metaclass confusion’. 40 >>> type(1).__hash__(1) == hash(1) 41 True 42 >>> type(int).__hash__(int) == hash(int) 43 True 44 45 这样做的另外的一个好处是, 对特殊方法的隐式调用同时也绕过 __getattribute__ 方法, 46 对该对象的的 metaclass 来说也是这样. 47 >>> class Meta(type): 48 ... def __getattribute__(*args): 49 ... print("Metaclass getattribute invoked") 50 ... return type.__getattribute__(*args) 51 ... 52 >>> class C(object, metaclass=Meta): 53 ... def __len__(self): 54 ... return 10 55 ... def __getattribute__(*args): 56 ... print("Class getattribute invoked") 57 ... return object.__getattribute__(*args) 58 ... 59 >>> c = C() 60 >>> c.__len__() # Explicit lookup via instance 61 Class getattribute invoked 62 10 63 >>> type(c).__len__(c) # Explicit lookup via type 64 Metaclass getattribute invoked 65 10 66 >>> len(c) # Implicit lookup 67 10 68 69 summarize, 70 '绕过' __getattribute__ 的机制最佳化了解释器的解释速度, 而这种机制是通过对特殊方法的灵活处理 71 实现的,即特殊方法必须在类对象本身上 set, 而不能通过 monkey patching,再通过 conventional'传统方式' 调用. 72 73 注, 74 conventional'传统方式' 调用 指的是诸如例子中 len() 跟 __len__() 的对应关系. 75 76 reference, 77 python doc, 78 https://docs.python.org/3/reference/datamodel.html#special-lookup