Python: Diamond Inheritance and super
参 Effective Python Item 25: Initialize Parent Classes with super。
Diamond Inheritance 就是 C++: Virtual Inheritance 里的 dread diamond,但是 Python 是允许你直接用这个继承结构的,不需要任何关键字来修饰,不像 C++ 里你不用 virtual
就会出错。
一个不使用 super
的例子:
class A:
def __init__(self):
self.value = 1
print("A Initialized!")
class B(A):
def __init__(self):
A.__init__(self) # ①
self.value += 2
class C(A):
def __init__(self):
A.__init__(self) # ①
self.value *= 2
class D(B, C):
def __init__(self):
B.__init__(self) # ②
C.__init__(self) # ②
d = D()
print(d.value)
print(D.__mro__)
# Output:
"""
A Initialized!
A Initialized!
2
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
"""
- ① 这里要么是显式调用
A.__init__(self)
或是直接super().__init__()
,否则你没有.value
的定义,后面+= 2
和*= 2
的操作会报错- 如果你是直接赋一个新值给
.value
,比如self.value = 2
,则可以不调用父类的__init__
A.__init__(self)
是类似@classmethod
的调用方式,super().__init__()
是 member function 的调用方式,因为super()
返回的是一个 proxy object 而不是一个类
- 如果你是直接赋一个新值给
- ② 这里还是显式调用,它的逻辑十分直白,就是字面意义(后面你会看到用
super()
会是多么的绕):D self = D.__new__()
B.__init__(self)
A.__init__(self); self.value += 2
$\Rightarrow$self.value
先赋值为 1,然后 +2,最后等于 3
C.__init__(self)
A.__init__(self); self.value *= 2
$\Rightarrow$self.value
先赋值为 1(原值 3 被覆盖掉),然后 *2,最后等于 2
我们可以看到,父类 A
被显式初始化了两次,而且 B.__init__(self)
对 .value
的赋值完全没有任何意义。我们若只想让 A
初始化一次,就得使用 super()
:
class A:
def __init__(self):
print("A Init Started!")
self.value = 1
print("A Init Ended!")
class B(A):
def __init__(self):
print("B Init Started!")
super().__init__()
self.value += 2
print("B Init Ended!")
class C(A):
def __init__(self):
print("C Init Started!")
super().__init__()
self.value *= 2
print("C Init Ended!")
class D(B, C):
def __init__(self):
super().__init__()
d = D()
print(d.value)
print(D.__mro__)
# Output:
"""
B Init Started!
C Init Started!
A Init Started!
A Init Ended!
C Init Ended!
B Init Ended!
4
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
"""
有点反常的是,最终的 .value
是 1 * 2 + 2 = 4
而不是 (1 + 2) * 2 = 6
。这里要深入讨论下 super()
的意义和执行顺序:
- 首先要明确一点,你在
D
内调用super()
,它的完整形态是super(D, self)
- 二来,根据 StackOverflow: Multiple inheritance in python3 with different signatures 的说法:The first argument to
super()
tells it what class to skip when looking for the next method to use! 所以在D
内调用super()
,展开成super(D, self)
,它的实际意义是:在D.__mro__
里找到第一个不是D
的 class,也就是B
,然后返回一个 proxy object,大概等同于(B, self)
。这么一来,super().__init__()
实际调用的是B.__init__(self)
! - 顺藤摸瓜,
B.__init__(self)
又调用了super(B, self)
,此时仍然是在D.__mro__
里找,第一个不是B
的 class,是C
,所以接着调用C.__init__(self)
所以整个的执行顺序大约可以表示为:
D self = D.__new__()
# D.__mro__ = (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
D.__init__(self) = {
super(D, self).__init__ == proxy(B, self).__init__() == B.__init__(self) {
print("B Init Started!")
super(B, self).__init__() == proxy(C, self).__init__() == C.__init__(self) {
print("C Init Started!")
super(C, self).__init__() == proxy(A, self).__init__() == A.__init__(self) {
print("A Init Started!")
self.value = 1
print("A Init Ended!")
}
self.value *= 2
print("C Init Ended!")
}
self.value += 2
print("B Init Ended!")
}
}
非常 counter-intuitive 的两点:
B
调用super()
实际找到的是C
,但是C
又不是B
的父类- 在
B
和C
的__init__
中,你完全可以先给.value
赋个值再调用super().__init__()
,不像 Java 里要求super()
constructor 一定要出现在第一行
Comments