为什么 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/ 但我真的不明白,他的解释似乎太技术化,他的例子(我理解的少数几个)也很远比他们应该解释的要点更复杂......这就是为什么我想要一个......更简单的解释。
Super() 必须遵循 MRO,即使对父 class 的继承会提出其他建议?
Super() 无法转到父 class 的父 class,因此如果父 class 中有一个 super,它将改为转到第二个继承 class?
此外,在真实的工作环境中看到钻石问题有点不相关但有多常见?似乎是一种非常复杂的工作方式。
您需要记住,MRO 不仅仅是简单的跟风。它创建一个类似图形的结构并解析相同的继承以避免重复。在您的第一个示例中,您创建了钻石继承。
A
/ \
B C
\ /
D
MRO 从第一层(直接父级 类),从左到右(B
然后 C
),然后是下一层向上寻求方法的解析,从左到右(这里只是 A
),依此类推。
在这种情况下,因为您同时从 A
继承了 B
和 C
,所以顶层解析为单个 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
的初始化两次,这并不需要发生。在长继承链或复杂的初始化例程中,这是非常低效的。
我正在看钻石问题,得到一个问题:
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/ 但我真的不明白,他的解释似乎太技术化,他的例子(我理解的少数几个)也很远比他们应该解释的要点更复杂......这就是为什么我想要一个......更简单的解释。
Super() 必须遵循 MRO,即使对父 class 的继承会提出其他建议?
Super() 无法转到父 class 的父 class,因此如果父 class 中有一个 super,它将改为转到第二个继承 class?
此外,在真实的工作环境中看到钻石问题有点不相关但有多常见?似乎是一种非常复杂的工作方式。
您需要记住,MRO 不仅仅是简单的跟风。它创建一个类似图形的结构并解析相同的继承以避免重复。在您的第一个示例中,您创建了钻石继承。
A
/ \
B C
\ /
D
MRO 从第一层(直接父级 类),从左到右(B
然后 C
),然后是下一层向上寻求方法的解析,从左到右(这里只是 A
),依此类推。
在这种情况下,因为您同时从 A
继承了 B
和 C
,所以顶层解析为单个 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
的初始化两次,这并不需要发生。在长继承链或复杂的初始化例程中,这是非常低效的。