56

我正在运行 Python 2.5,所以这个问题可能不适用于 Python 3。当您使用多重继承创建菱形类层次结构并创建派生类的对象时,Python 会做正确的事情 (TM)。它调用派生类的构造函数,然后是从左到右列出的父类,然后是祖父类。我熟悉 Python 的MRO;那不是我的问题。我很好奇从 super 返回的对象实际上如何以正确的顺序与父类中的 super 调用进行通信。考虑这个示例代码:

#!/usr/bin/python

class A(object):
    def __init__(self): print "A init"

class B(A):
    def __init__(self):
        print "B init"
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        super(D, self).__init__()

x = D()

代码做直观的事情,它打印:

D init
B init
C init
A init

但是,如果您在 B 的 init 函数中注释掉对 super 的调用,则不会调用 A 和 C 的 init 函数。这意味着 B 对 super 的调用以某种方式知道 C 在整个类层次结构中的存在。我知道 super 返回一个带有重载 get 运算符的代理对象,但是 D 的 init 定义中 super 返回的对象如何将 C 的存在传达给 B 的 init 定义中 super 返回的对象?后续调用 super 使用的信息是否存储在对象本身上?如果是这样,为什么不是 super 而不是 self.super?

编辑:Jekke 非常正确地指出它不是 self.super 因为 super 是类的属性,而不是类的实例。从概念上讲这是有道理的,但实际上 super 也不是类的属性!您可以在解释器中通过创建两个类 A 和 B(其中 B 继承自 A)并调用dir(B). 它没有super__super__属性。

4

5 回答 5

34

将您的代码更改为此,我认为它会解释一些事情(大概super是在看,比如说,在哪里B__mro__):

class A(object):
    def __init__(self):
        print "A init"
        print self.__class__.__mro__

class B(A):
    def __init__(self):
        print "B init"
        print self.__class__.__mro__
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        print self.__class__.__mro__
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        print self.__class__.__mro__
        super(D, self).__init__()

x = D()

如果你运行它,你会看到:

D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

另外值得一试的是 Python 的 Super 很漂亮,但你不能使用它

于 2009-03-03T17:17:53.700 回答
16

我在下面提供了一堆链接,这些链接比我希望的更详细、更准确地回答了您的问题。但是,我也会用我自己的话回答您的问题,以节省您的时间。我会用分数表示-

  1. super 是一个内置函数,而不是一个属性。
  2. Python 中的每个类型(类)都有一个__mro__属性,用于存储该特定实例的方法解析顺序。
  3. 对 super 的每次调用都采用 super(type[, object-or-type]) 的形式。让我们暂时假设第二个属性是一个对象。
  4. 在超级调用的起点,对象是派生类的类型(比如 DC)。
  5. super 在指定为第一个参数的类之后(在本例中为 DC 之后的类)在 MRO 中的类中查找匹配的方法(在您的情况下__init__)。
  6. 当找到匹配方法时(比如在类BC1中),它被调用。
    (这个方法应该使用 super,所以我假设它确实如此 - 请参阅 Python 的 super 很漂亮但不能使用 - 下面的链接)然后该方法会导致在对象的类的 MRO 中搜索下一个方法,在右侧BC1
  7. 重复冲洗,直到找到并调用所有方法。

你的例子的解释

 MRO: D,B,C,A,object  
  1. super(D, self).__init__()叫做。isinstance(self, D) => True
  2. 在 D 右侧的类中 搜索 MRO 中的下一个方法。

    B.__init__找到并打电话


  1. B.__init__来电 super(B, self).__init__()

    isinstance(self, B) => False
    isinstance(self, D) => True

  2. 因此,MRO 是相同的,但搜索继续到 B 的右侧,即 C、A、对象被一一搜索。调用下一个__init__找到的。

  3. 等等等等。

super 的解释
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
使用 super 时要注意的事项
http://fuhm.net/super-harmful/
Pythons MRO 算法:
http ://www.python.org/download/releases/2.3/mro/
super 的文档:
http ://docs.python.org/library/functions.html
这个页面的底部有一个关于 super 的很好的部分:
http:// /docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

我希望这有助于清除它。

于 2009-03-03T17:58:41.900 回答
6

只是猜测:

self在所有四个方法中都引用同一个对象,即 class D。因此,在 中B.__init__(),对 to 的调用super(B,self)知道整个钻石血统,self并且它必须从 'after' 获取方法B。在这种情况下,它是C类。

于 2009-03-03T17:14:45.220 回答
3

super()知道完整的类层次结构。这是 B 的 init 内部发生的情况:

>>> super(B, self)
<super: <class 'B'>, <D object>>

这解决了中心问题,

D 的 init 定义中 super 返回的对象如何将 C 的存在传达给 B 的 init 定义中 super 返回的对象?

也就是说,在 B 的 init 定义中,self是 的一个实例D,因此传达了 的存在C。例如C可以在type(self).__mro__.

于 2012-12-18T20:57:49.843 回答
2

Jacob 的回答显示了如何理解问题,而 batbrat 的回答显示了细节,hrr 的回答直截了当。

他们没有从您的问题中涵盖(至少没有明确)的一件事是这一点:

但是,如果您在 B 的 init 函数中注释掉对 super 的调用,则不会调用 A 和 C 的 init 函数。

要理解这一点,请将 Jacob 的代码更改为在 A 的 init 上打印堆栈,如下所示:

import traceback

class A(object):
    def __init__(self):
        print "A init"
        print self.__class__.__mro__
        traceback.print_stack()

class B(A):
    def __init__(self):
        print "B init"
        print self.__class__.__mro__
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        print self.__class__.__mro__
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        print self.__class__.__mro__
        super(D, self).__init__()

x = D()

B看到那行super(B, self).__init__()实际上是在调用,这有点令人惊讶C.__init__(),因为C它不是B.

D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
  File "/tmp/jacobs.py", line 31, in <module>
    x = D()
  File "/tmp/jacobs.py", line 29, in __init__
    super(D, self).__init__()
  File "/tmp/jacobs.py", line 17, in __init__
    super(B, self).__init__()
  File "/tmp/jacobs.py", line 23, in __init__
    super(C, self).__init__()
  File "/tmp/jacobs.py", line 11, in __init__
    traceback.print_stack()

发生这种情况是因为super (B, self)不是'调用 B 的基类版本__init__'。相反,它是 '调用__init__右侧的第一个类,B它存在于self's上__mro__并且具有这样的属性

因此,如果您在 B 的 init 函数中注释掉对 super 的调用,则方法堆栈将停止在B.__init__,并且永远不会到达Cor A

总结一下:

  • 无论哪个类引用它,self始终是对实例的引用,并且它__mro____class__保持不变
  • super() 查找查找 . 上当前类右侧的类的方法__mro__。由于__mro__保持不变,发生的情况是它作为列表而不是树或图进行搜索。

关于最后一点,请注意 MRO 算法的全称是C3 超类线性化。也就是说,它将结构扁平化为列表。当不同的super()调用发生时,它们实际上是在迭代该列表。

于 2017-07-09T21:48:22.220 回答