来自refence中的Python数据模型章节的Special method lookup for new-style classes section:
对于新式类,只有在定义时,才能保证对特殊方法的隐式调用才能正常工作 对象的类型,而不是对象的实例字典。这种行为是以下代码引发的原因 一个例外(与旧式类的等效示例不同):
>>> class C(object): ... 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
我无法用大胆的方式抓住这些词......
答案 0 :(得分:2)
要了解这里发生了什么,您需要对传统的属性查找过程有一个(基本的)理解。以典型的面向对象编程示例为例 - fido
是Dog
:
class Dog(object):
pass
fido = Dog()
如果我们说fido.walk()
,Python所做的第一件事就是在walk
中寻找一个名为fido
的函数(作为fido.__dict__
中的一个条目)并调用它没有参数 - 所以,一个被定义为这样的东西:
def walk():
print "Yay! Walking! My favourite thing!"
fido.walk = walk
和fido.walk()
将有效。如果我们没有这样做,它会在walk
中查找属性type(fido)
(Dog
),并以实例作为第一个参数调用它(即{{1}这是由我们在Python中定义方法的通常方式触发的:
self
现在,当您致电class Dog:
def walk(self):
print "Yay! Walking! My favourite thing!"
时,它最终会调用特殊方法repr(fido)
。它可能(很差,但说明性地)定义如下:
__repr__
但是,大胆的文字说这样做也是有道理的:
class Dog:
def __repr__(self):
return 'Dog()'
在我刚刚描述的查找过程中,它首先查找的是一个名为 repr(Dog)
的方法,分配给__repr__
...嘿,看,有一个,因为我们只是很差但是说明性地定义它。所以,Python调用:
Dog
它在我们脸上爆炸:
Dog.__repr__()
因为>>> Dog.__repr__()
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
Dog.__repr__()
TypeError: __repr__() takes exactly 1 argument (0 given)
期望__repr__()
实例作为其Dog
参数传递给它。我们可以这样做,以使其工作:
self
但是,我们每次编写自定义class Dog:
def __repr__(self=None):
if self is None:
# return repr of Dog
# return repr of self
函数时都需要执行此操作。它需要知道如何找到类的__repr__
是一个问题,但不是一个问题 - 它可以委托给__repr__
自己的类(Dog
)和将type(Dog)
称为__repr__
作为其Dog
- 参数:
self
但首先,如果类名在将来发生变化,这会中断,因为我们需要在同一行中提及它两次。但更大的问题是,这基本上将成为样板:99%的实现只会委托链,或者忘记,因此是错误的。因此,Python采用这些段落中描述的方法 - if self is None:
return type(Dog).__repr__(Dog)
跳过查找附加到repr(foo)
的{{1}},然后直接进入:
__repr__
答案 1 :(得分:0)
您必须记住的是,类是元类的实例。某些操作不仅需要在实例上执行,还需要在类型上执行。如果实例上的方法被运行,那么它将失败,因为实例上的方法(在这种情况下实际上是一个类)将需要类的实例而不是元类。
class MC(type):
def foo(self):
print 'foo'
class C(object):
__metaclass__ = MC
def bar(self):
print 'bar'
C.foo()
C().bar()
C.bar()
答案 2 :(得分:0)
普通属性检索obj.attr
在attr
的instance属性和class属性中查找obj
。它在 object.__getattribute__
和 type.__getattribute__
中定义。
隐式特殊方法调用special(obj, *args, **kwargs)
(例如hash(1)
)在__special__
的类属性(例如__hash__
)中查找obj
(例如1
) }}),绕过obj
的实例属性,而不是执行正常的属性检索obj.__special__
,并调用它。基本原理是 obj
的实例属性可能需要接收器参数(通常称为 self
),它是 obj
要调用的实例(例如函数属性) 而 special(obj, *args, **kwargs)
不提供一个,这与 obj
的类属性相反,它可能需要一个接收器参数(通常称为 self
),它是类 {{1} 的一个实例} 被调用(例如函数属性)和type(obj)
提供了一个:special(obj, *args, **kwargs)
。
特殊方法 obj
接受单个参数。比较这两个表达式:
__hash__
>>> 1 .__hash__
<method-wrapper '__hash__' of int object at 0x103c1f930>
>>> int.__hash__
<slot wrapper '__hash__' of 'int' objects>
中检索绑定到 vars(type(1))['__hash__'].__get__(1)
的方法 1
。所以 class 属性需要一个接收器参数,它是要调用的 vars(type(1))['__hash__']
的一个实例,我们已经提供了一个:type(1)
。1
中检索函数 vars(int)['__hash__'].__get__(None, int)
。所以实例属性需要一个接收器参数,它是要调用的 vars(int)['__hash__']
的一个实例,我们还没有提供。int
由于内置函数 >>> 1 .__hash__()
1
>>> int.__hash__(1)
1
采用单个参数,hash
可以提供第一次调用(类属性调用)所需的 hash(1)
而 1
不能提供第二次调用(实例属性调用)中所需的 hash(int)
。因此,1
应该绕过实例属性 hash(obj)
并直接访问类属性 vars(obj)['__hash__']
:
vars(type(obj))['__hash__']