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 一定要出现在第一行
留下评论