为什么 super() 继承了 "wrong" class?

Why does super() inherits the "wrong" class?

我正在看钻石问题,得到一个问题:


class A:
    def __init__(self):
        print("This is class A")

class B(A):
    def __init__(self):
        print("This is class B")
        super().__init__()

class C(A):
    def __init__(self):
        print("This is class C")
        super().__init__()

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


i = D()

This is class D
This is class B
This is class C
This is class A

它按预期工作,这很好,但我想知道为什么 class B 中的 super().__init__() 不会转到 class A 而是调用 C。

如果 class 有一个 super() 并且它继承自父 class,它应该去那里。

如果我在 B 上删除它,代码将不会到达 C 或 A。

我知道 MRO 以及它实际如何按预期进行:

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

但我不知道为什么。

这比非 super() 代码的实现具有相同的 MRO 但 A 被打印两次非常奇怪:


class A:
    def __init__(self):
        print("This is class A")

class B(A):
    def __init__(self):
        print("This is class B")
        A.__init__(self)

class C(A):
    def __init__(self):
        print("This is class C")
        A.__init__(self)

class D(B, C):
    def __init__(self):
        print("This is class D")
        B.__init__(self)
        C.__init__(self)


i = D()
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

这是相反的,我知道 MRO 是正确的,但奇怪的是实际执行不是这样的:

This is class D
This is class B
This is class A
This is class C
This is class A

我想知道 super() 行为背后的逻辑是什么。

当在网上询问这个问题时,几乎每个人都将我链接到这个:https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ 但我真的不明白,他的解释似乎太技术化,他的例子(我理解的少数几个)也很远比他们应该解释的要点更复杂......这就是为什么我想要一个......更简单的解释。

您需要记住,MRO 不仅仅是简单的跟风。它创建一个类似图形的结构并解析相同的继承以避免重复。在您的第一个示例中,您创建了钻石继承。

   A
  / \
 B   C
  \ /
   D

MRO 从第一层(直接父级 类),从左到右(B 然后 C),然后是下一层向上寻求方法的解析,从左到右(这里只是 A),依此类推。

在这种情况下,因为您同时从 A 继承了 BC,所以顶层解析为单个 A 并创建了上面的菱形图.

让我们看看你的第二个例子:

class D(B, C):
    def __init__(self):
        print("This is class D")
        B.__init__(self)
        C.__init__(self)

通过这种方式实施,您可以有效地绕过 MRO。你拿走了继承钻石,并把它变成了继承橄榄叉,看起来像这样:

 A   A
 |   |
 B   C
  \ /
   D

因此,您调用了 A 的初始化两次,这并不需要发生。在长继承链或复杂的初始化例程中,这是非常低效的。