Python 为什么在隐式查找特殊方法时要绕过实例属性?
从Python文档中“数据模型”一章的开头(粗体强调): 对于新样式的类,只有在 对象的类型,不在对象的实例字典中。这种行为是以下代码引发错误的原因 异常(与旧式类的等效示例不同): 以这种方式错误地调用类的未绑定方法有时被称为“元类” 在查找特殊方法时绕过实例可避免混淆:Python 为什么在隐式查找特殊方法时要绕过实例属性?,python,function,methods,method-call,Python,Function,Methods,Method Call,从Python文档中“数据模型”一章的开头(粗体强调): 对于新样式的类,只有在 对象的类型,不在对象的实例字典中。这种行为是以下代码引发错误的原因 异常(与旧式类的等效示例不同): 以这种方式错误地调用类的未绑定方法有时被称为“元类” 在查找特殊方法时绕过实例可避免混淆: >>> 1 .__hash__() == hash(1) True >>> int.__hash__() == hash(int) Traceback (most recent call
>>> 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
我不能很好地理解粗体字…你必须记住的是类是它们的元类的实例。有些操作不仅需要在实例上执行,还需要在类型上执行。如果实例上的方法被运行,那么它将失败,因为实例上的方法(在本例中实际上是一个类)将需要类的实例,而不是元类
class MC(type):
def foo(self):
print 'foo'
class C(object):
__metaclass__ = MC
def bar(self):
print 'bar'
C.foo()
C().bar()
C.bar()
要了解这里发生了什么,您需要对传统的属性查找过程有一个(基本)的了解。举一个典型的面向对象编程入门示例-
fido
是一只Dog
:
class Dog(object):
pass
fido = Dog()
如果我们说fido.walk()
,Python做的第一件事就是在fido
中寻找一个名为walk
的函数(作为fido.\uu dict\uuu
中的一个条目),然后不带参数地调用它——因此,定义了这样一个函数:
def walk():
print "Yay! Walking! My favourite thing!"
fido.walk = walk
class Dog:
def __repr__(self):
return 'Dog()'
和fido.walk()
将起作用。如果我们没有这样做,它将在type(fido)
(即Dog
)中查找属性walk
,并使用实例作为第一个参数调用它(即,self
)-这是由我们在Python中定义方法的通常方式触发的:
class Dog:
def walk(self):
print "Yay! Walking! My favourite thing!"
现在,当您调用repr(fido)
时,它最终会调用特殊的方法\uuuuuu repr\uuuuu
。它可能(很糟糕,但说明性地)定义如下:
def walk():
print "Yay! Walking! My favourite thing!"
fido.walk = walk
class Dog:
def __repr__(self):
return 'Dog()'
但是,粗体文本表示,这样做也有意义:
repr(Dog)
在我刚才描述的查找过程中,它查找的第一件事是分配给狗的名为的方法。。。嘿,看,有一个,因为我们对它的定义很差,但很有说明性。因此,Python调用:
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)
但是,我们需要在每次编写自定义的\uuuu repr\uuu
函数时都这样做。它需要知道如何找到类的\uuu repr\uuuu
是一个问题,但问题不大-它可以委托给Dog
自己的类(type(Dog)
),并调用它的\uu repr\uu
,使用Dog
作为它的self
参数:
if self is None:
return type(Dog).__repr__(Dog)
但首先,如果类名将来发生更改,这会中断,因为我们需要在同一行中提到它两次。但更大的问题是,这基本上是一个样板:99%的实现只会向上委派,或者忘记委派,因此会出现错误。因此,Python采用了这些段落中描述的方法-repr(foo)
跳过查找附加到foo
的\uuuuu repr\uuuu
,直接转到:
type(foo).__repr__(foo)
普通属性检索obj.attr
在obj
的实例属性和类属性中查找attr
。它在object.\uuuuu getattribute.\uuuuu
和type.\uuuu getattribute.\uuuu
中定义
隐式特殊方法调用special(obj,*args,**kwargs)
(例如hash(1)
)在obj
(例如1
)的类属性中查找\uu special\uuuuuuuuuuuuuuuu>(例如),绕过obj
的实例属性,而不是执行正常的属性检索obj.\uuuuu special\uuuu
,并调用它。基本原理是,obj
的实例属性可能需要一个接收方参数(通常称为self
),该参数是要调用的obj
的实例(例如函数属性),而特殊(obj,*args,**kwargs)
不提供该参数,与可能需要接收方参数(通常称为self
)的obj
的类属性相反,接收方参数是要调用的类类型(obj)
的实例(例如函数属性)和特殊(obj,*args,**kwargs)
提供了一个:obj
例子
特殊方法\uuuuuuuuuuuuuuuuuuuu
只接受一个参数。比较这两个表达式:
>>1.\uu散列__
>>>整数散列__
- 第一个表达式从class属性
vars(type(1))[''''''''''''''.''''.''''.'''.''''.'获取绑定到1
的方法vars(type(1))[''.''''.''''.''''.'.'''''.''.''''.'''.'.''.'hash\code>。因此class属性需要调用一个receiver参数,它是类型(1)
的实例,我们已经提供了一个:1
- 第二个表达式从实例属性
vars(int)['''uuuuuuuuuuu hash\uuuuu'].\uuuuuuuu get\uuuuuuuuuuuuu(None,int)
检索函数。因此instance属性需要调用一个receiver参数,该参数是int
的实例,我们还没有提供
>>>1.\uuuu散列
1.
>>>整数散列(1)
1.
由于内置函数hash
只接受一个参数,hash(1)
可以提供第一次调用(类属性调用)所需的1
,而hash(int)
不能提供第二次调用(实例属性调用)所需的1
。因此,hash(obj)
应该绕过实例属性vars(obj)['''uuu hash\uuu']
,直接访问类属性vars(type(obj))[''uu hash\uu']
:
>>散列(1)=vars(类型(1))[''散列]__