问题引出:
Question 1
class A():
def __init__(self):
print("enter A…")
print("leave A…")
class B(A):
def __init__(self):
print("enter B…")
A.__init__(self)
print("leave B…")
class C(A):
def __init__(self):
print("enter C…")
A.__init__(self)
print("leave C…")
class D(B, C):
def __init__(self):
print("enter D…")
B.__init__(self)
C.__init__(self)
print("leave D…")
if __name__ == '__main__':
d = D()
当运行这段代码的时候,输出是什么?
Question 2
class Displayer():
def display(self, message):
print(message)
class LoggerMixin():
def log(self, message, filename='logfile.txt'):
with open(filename, 'a') as fh:
fh.write(message)
def display(self, message):
super().display(message)
self.log(message)
class MySubClass(LoggerMixin, Displayer):
def log(self, message):
super().log(message, filename='subclasslog.txt')
subclass = MySubClass()
subclass.display("This string will be shown and logged in subclasslog.txt")
当执行这段代码的时候,执行结果是什么?
问题解答
1 、 当运行问题 1 的代码时输出:
enter D…
enter B…
enter A…
leave A…
leave B…
enter C…
enter A…
leave A…
leave C…
leave D…
我们注意到 A
类的初始化方法被执行了两次。这种现象就是 菱形继承 所造成的结果。
多重继承容易导致钻石继承(菱形继承)问题,上边代码实例化 D
类后我们发现 A 被前后进入了两次。假设我们在 A
的初始化方法里面有许多代码只允许初始化一次,那这样 D
的实例化就造成了意料之外的结果。这显然是不被允许的。
MRO
为了解决这个问题,Python 弄了一叫做 MRO
的东西。事实上,对于你定义的每一个类,Python 会计算出一个方法解析顺序(Method Resolution Order, MRO)列表,它代表了类继承的顺序,我们可以通过 类名.__mro__
的形式来查看当前类的继承链。
那这个 MRO 列表的顺序是怎么定的呢,它是通过一个 C3 线性化算法来实现的,这里我们就不去深究这个算法了,感兴趣的读者可以自己去了解一下,总的来说,一个类的 MRO 列表就是合并所有父类的 MRO 列表,并遵循以下三条原则:
- 子类永远在父类前面
- 如果有多个父类,会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
super
那怎么去使用这条继承连,或者只说我们如何去让我们的代码按这条继承链来处理继承顺序呢?Python 提供了一个叫做 super
的方法来帮助我们处理这个问题。
在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用 super
来实现。
当我把问题 1 的代码改成这样的时候,A
初始化方法重复执行的问题就不会再出现了。
class A():
def __init__(self):
print("enter A…")
print("leave A…")
class B(A):
def __init__(self):
print("enter B…")
super().__init__() # 使用 super 替代
print("leave B…")
class C(A):
def __init__(self):
print("enter C…")
super().__init__() # 使用 super 替代
print("leave C…")
class D(B, C):
def __init__(self):
print("enter D…")
super().__init__() # 使用 super 替代
print("leave D…")
if __name__ == '__main__':
d = D()
输出结果:
enter D…
enter B…
enter C…
enter A…
leave A…
leave C…
leave B…
leave D…
可以看到,重复执行 A
类的初始化方法的现象就不会再出现了。
我们需要明白,super 其实和父类没有实质性的关联。它的工作原理大概是这样:
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
其中,cls
代表类,inst
代表实例,上面的代码做了两件事:
- 获取
inst
的 MRO 列表 - 查找
cls
在当前 MRO 列表中的index
, 并返回它的下一个类,即mro[index + 1]
当你使用 super(cls, inst)
(现在可以简写成 super()
) 时,Python 会在 inst
的 MRO 列表上搜索 cls
的下一个类。
到这里,我们已经基本明白了 super()
的用途。
2 、 回到问题 2,其实我们已经基本能够看懂代码。为什么 LoggerMixin
类能够找到 Displayer
这个类下面的 display
方法呢?其实就是上面我们讲的 super
的工作原理,它会帮我们找到当前类在继承链中的下一个类。查看 MySubClass
的继承链:
MySubClass.__mro__
输出:
(<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>)
可以看到 Displayer
在继承链上确实是在 LoggerMixin
的下一个位置。
还有一点需要注意的是,我们的 LoggerMixin
类还调用了 self.log()
方法。这个看似好像要直接调用 LoggerMixin
的 log
方法,其实并不是的。
LoggerMixin
的 display()
方法在当前语境中的 self
,其实是 MySubClass
类的对象,因此对于 MySubClass
类的对象,想要调用 log
方法,是直接调用自己类中的 log
方法,也就是 MySubClass.log()
方法,而不是 LoggerMixin.log()
方法的。
而又因为 MySubClass.log()
方法调用了 super().log()
方法,这才根据继承链寻找最近的父类,才找到了 LoggerMixin
类中的 log()
方法进行调用。
Mixin
为什么 问题 2 的类名要使用 Mixin
?
Mixin
类跟普通父类的继承顺序可以调换吗?
关于 Mixin
的问题,在以后其他文章再做总结。