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

从Python文档中“数据模型”一章的开头(粗体强调):

对于新样式的类,只有在 对象的类型,不在对象的实例字典中。这种行为是以下代码引发错误的原因 异常(与旧式类的等效示例不同):

以这种方式错误地调用类的未绑定方法有时被称为“元类” 在查找特殊方法时绕过实例可避免混淆:

>>> 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))[''散列]__