本文摘自朱雷老师所著《Python工匠》一书第9章内容,因为很多内容,阅读时依然一知半解,特做笔记加强认知,及后续加强理解。
本文通过几段代码,几个简单类继承关系,重点说明和理解如下一句话:
在许多人的印象中,super()是一个调用父类方法的工具函数,但这么说并不准确,super()使用的其实不是当前类的父类,而是它在MRO链条里的上一个类。
许多编程语言在处理继承关系时,只允许子类继承一个父类,而Python里的一个类可以同时继承多个父类。这让我们的模型设计变得更灵活,但同时也带来一个新的问题:“在复杂的继承关系下,如果确认子类的某个方法会用到哪个父类?”
以下面的代码为例:
class A:
def say(self):
print("I'm A")
class B(A):
pass
class C(A):
def say(self):
print("I'm C")
class D(B, C):
pass
D同时继承B和C两个父类,而B和C都是A的子类。如果调用D类实例的say()方法,系统是调用A类的say()方法输出“I ' m A”,还是调用C类的say()方法输出“I ' m C”呢?答案是:
>>>D().say()
I ' m C
这是因为,在解决多重继承的方法的优先级问题时,Python使用了一种名为MRO(method resolution order)的算法。该算法会遍历类的所有基类,并将它们按照优先级从高到低排序。
调用类的mro()方法,可以看到按照MRO算法排好序的基类列表:
>>>D.mro()
[
故调用D.say(),首先从D类中寻找,然后B类中寻找,再然后从类中寻找,再然后从A类中寻找,最先找到,优先调用。上面
上面代码的类继承B和C类,D类中没有定义say()方法,B中也没有定义say()方法,C类中定义了say()方法,最先找到,所以被调用,输出I ' m C”。
如果B类中也定义了say()方法如下:
class A:
def say(self):
print("I'm A")
class B(A):
pass
def say(self):
print("I'm B")
class C(A):
def say(self):
print("I'm C")
class D(B, C):
pass
>>>D().say()
I ' m B
如果B和C类都没有定义say()方法,则因为B和C都继承了A类,则调用A类的say()方法,会输出“I ' m A”
当然,如果D类中也重新定义了say()方法,则优先调用D类中定义的say()方法。
故,Python中,当调用子类的某个方法时,Python会按照MRO列表,从前向后寻找这个方法,假如某个类实现了这个方法,就直接调用返回。
MRO与super()
基于MRO算法的基类优先级列表,不光定义了类方法的找寻顺序,还影响了另一个常见的内置函数:super()。
在许多人的印象中,super()是一个调用父类方法的工具函数,但这么说并不准确,super()使用的其实不是当前类的父类,而是它在MRO链条里的上一个类。
举个例子:
class A:
def __init__(self) -> None:
print('I am A')
super().__init__()
class B(A):
def __init__(self) -> None:
print('I am B')
super().__init__()
class D1(B):
pass
>>>D1() # 实例化D1类,输出如下:
'I am B'
'I am A'
在上面的单一继承关系下,实例化D1类,D1类中没有定义实例化__init__()方法,则调用继承的B类的实例化__init__()方法,先执行print(' I am B') ,因为B继承了A,B类中执行super().__init__()方法,则又输出'I am A'
如果D类中也定义了__init__()方法,如下:
class A:
def __init__(self) -> None:
print('I am A')
super().__init__()
class B(A):
def __init__(self) -> None:
print('I am B')
super().__init__()
class D1(B):
def __init__(self) -> None:
print('I am D')
super().__init__()
>>>D1() # 实例化D1类,输出如下:
'I am D'
'I am B'
'I am A'
以上比较好理解,super()看上去就像是在调用父类的方法。但是,如果稍微调整一下继承关系,把C类加入到继承关系链中,D2类继承B和C类:
class A:
def __init__(self) -> None:
print('I am A')
super().__init__()
class B(A):
def __init__(self) -> None:
print('I am B')
super().__init__()
class C(A):
def __init__(self) -> None:
print('I am C')
super().__init__()
class D1(B):
def __init__(self) -> None:
print('I am D')
super().__init__()
class D2(B,C):
pass
>>>D2() # 实例化D2类,输出如下:
'I am B'
'I am C'
'I am A'
上面代码,D2类按照顺序继承B和C,D2类中没有定义实例化__init__()方法,对D2类实例化:
1)首先执行B类的__init__()方法,先执行print('I am B')输出'I am B'
2)然后执行super().__init__()方法,B类继承了A类,但是执行的是C类中的__init__()方法,先执行了print('I am C')输出'I am C',然后执行C类中的super().__init__()方法
3)C类继承了A类,执行了print('I am A')输出'I am A'
当我们在继承关系中加入C类后,B.__init__()里的super()不会再直接找到B的父类A,而是会定位到当前MRO链条里的下一个类,一个看上去和B毫不相关的类:C。
大多数情况下,可能需要的并不是多重继承,也许只是一个更准确的抽象模型,在该模型下,最普通的继承关系就能完美解决问题。