为什么我们在隐式查找特殊方法时绕过实例属性?

Det*_*ant 5 python methods function method-call

来自Python文档中\xe2\x80\x98数据模型\xe2\x80\x99章节的\xe2\x80\x98新样式类的特殊方法查找\ xe2\x80\x99部分(粗体强调是我的):

\n
\n

对于新式类,只有在 object\xe2\x80\x99s 类型上定义特殊方法的隐式调用才能保证正确工作,而不是在 object\xe2\x80\x99s 实例字典中定义。这种行为就是以下代码引发异常的原因(与旧式类的等效示例不同):

\n
>>> class C(object):\n...     pass\n...\n>>> c = C()\n>>> c.__len__ = lambda: 5\n>>> len(c)\nTraceback (most recent call last):\n  File "<stdin>", line 1, in <module>\nTypeError: object of type \'C\' has no len()\n
Run Code Online (Sandbox Code Playgroud)\n

此行为背后的基本原理在于由所有对象(包括类型对象)实现的许多特殊方法,例如__hash__()和\n 。__repr__()如果这些方法的隐式查找\n使用传统的查找过程,则在类型对象本身上调用时它们将失败:

\n
>>> 1 .__hash__() == hash(1)\nTrue\n>>> int.__hash__() == hash(int)\nTraceback (most recent call last):\n  File "<stdin>", line 1, in <module>\nTypeError: descriptor \xe2\x80\x99__hash__\xe2\x80\x99 of \xe2\x80\x99int\xe2\x80\x99 object needs an argument\n
Run Code Online (Sandbox Code Playgroud)\n

错误地尝试以这种方式调用类的未绑定方法有时被称为 \xe2\x80\x98metaclass\nconfusion\xe2\x80\x99,并且可以通过在查找特殊方法时绕过实例来避免:

\n
>>> type(1).__hash__(1) == hash(1)\nTrue\n>>> type(int).__hash__(int) == hash(int)\nTrue\n
Run Code Online (Sandbox Code Playgroud)\n
\n

我听不懂粗体字\xe2\x80\xa6

\n

lvc*_*lvc 4

要了解这里发生的情况,您需要对传统属性查找过程有(基本)了解。举一个典型的面向对象编程入门示例 -fidoDog

class Dog(object):
    pass

fido = Dog()
Run Code Online (Sandbox Code Playgroud)

如果我们说fido.walk(),Python 做的第一件事就是查找walk在 中调用的函数fido(作为 中的条目fido.__dict__)并在不带参数的情况下调用它 - 所以,它的定义如下:

def walk():
   print "Yay! Walking! My favourite thing!"

fido.walk = walk
Run Code Online (Sandbox Code Playgroud)

并且fido.walk()会起作用。walk如果我们没有这样做,它会在type(fido)(即) 中查找属性Dog并使用实例作为第一个参数调用它 (即self) - 这是由我们在 Python 中定义方法的常用方式触发的:

class Dog:
    def walk(self):
         print "Yay! Walking! My favourite thing!"
Run Code Online (Sandbox Code Playgroud)

现在,当您调用 时repr(fido),它最终会调用特殊方法__repr__。它可能(不太好,但说明性地)定义如下:

class Dog:
    def __repr__(self):
          return 'Dog()'
Run Code Online (Sandbox Code Playgroud)

但是,粗体文字表明这样做也是有意义的:

 repr(Dog)
Run Code Online (Sandbox Code Playgroud)

在我刚刚描述的查找过程中,它查找的第一件事是一个名为“__repr__分配给Dog...”的方法,嘿,看,有一个,因为我们只是很糟糕但说明性地定义了它。所以,Python 调用:

Dog.__repr__()
Run Code Online (Sandbox Code Playgroud)

它在我们脸上爆炸:

>>> Dog.__repr__()
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    Dog.__repr__()
TypeError: __repr__() takes exactly 1 argument (0 given)
Run Code Online (Sandbox Code Playgroud)

因为__repr__()期望将一个Dog实例作为其参数传递给它self。我们可以这样做来让它工作:

class Dog:
    def __repr__(self=None):
       if self is None:
           # return repr of Dog
       # return repr of self
Run Code Online (Sandbox Code Playgroud)

但是,每次编写自定义__repr__函数时,我们都需要执行此操作。它需要知道如何找到__repr__类的 ,这是一个问题,但问题不大——它可以委托给Dog自己的类 ( type(Dog)) 并调用它的__repr__withDog作为它的self参数:

 if self is None:
   return type(Dog).__repr__(Dog)
Run Code Online (Sandbox Code Playgroud)

但首先,如果类名将来发生变化,这就会中断,因为我们需要在同一行中提及它两次。但更大的问题是,这基本上是样板文件:99% 的实现只会向上委托链,或者忘记这样做,从而出现错误。因此,Python 采用这些段落中描述的方法 -repr(foo)跳过查找__repr__附加到foo,并直接进入:

type(foo).__repr__(foo) 
Run Code Online (Sandbox Code Playgroud)