多重继承
Python同C++一样,允许多重继承。写法上也十分简洁:
classA:pass
classB:pass
classC(A,B):
pass
print(C.__bases__)
# (, )
这样,类和的属性就被继承了。另外还存在一种多级继承的概念:
classA:pass
classB(A):pass
classC(B):pass
层级关系可以写作:。
下面来看这样一个问题。假如有一个方法在和中都定义了,在中没有定义,那么的对象调用该方法时调用的是谁的方法?
classA:
defm(self):
print('I\'m A')
classB:
defm(self):
print("I'm B")
classC(A,B):
pass
c=C()
c.m()
# I'm A
改变一下继承顺序再看一看:
classC(B,A):
pass
c=C()
c.m()
# I'm B
好像能得到一个初步结论了,谁声明在前面就调用谁,很像广度优先算法(Breath-first Search)。
如果是这样的关系呢:
classA:
defm(self):
print('I\'m A')
classB:
defm(self):
print("I'm B")
classC(A):
pass
classD(B):
defm(self):
print("I'm D")
classE(C,D):
pass
classF(D,C):
pass
e=E()
f=F()
e.m()
# I'm A
f.m()
# I'm D
我们利用图来看一下他们的关系(图中粉色线表示第二顺位):
从上面的运行结果来看,寻找方法的算法很像是深度优先算法(DFS,Depth-first Search)。实际上,Python中方法的查找算法称作C3算法,查找的过程称作方法解析顺序(MRO,Method Resolution Order)。
MRO
关于详细的C3算法可以自行查找。这里只说明其中所蕴含的两个问题:
单调性问题
无效重写问题
采用C3算法的原因即在于它解决了上述两个问题而无论BFS或DFS都无法完美解决。
单调性问题
如上图的继承关系,当的对象调用方法时,要求先在的父类中搜索后再去分支中搜索。这是合理的,因为的这一条独立的继承序列还没有搜索完毕。搜索顺序是:。显然,BFS无法解决这个问题,而DFS可以。
无效重写问题
对于上图的继承关系(菱形继承问题),当的对象调用方法时,要求先在的所有子类中搜索完毕后再搜索。这样能够保证子类中重写的方法可以被搜索到。如果按照DFS来做,那么中重写的将被截胡,永远不会被调用到,重写就没有意义了。这时候则需要BFS方法来做。搜索顺序为:。C3算法解决了上面两个问题。我们可以通过类的属性或方法来查看它的MRO顺序:
# 上面的E和F
frompprintimportpprint
pprint(E.__mro__)
# (,
# ,
# ,
# ,
# ,
# )
pprint(F.mro())
# [,
# ,
# ,
# ,
# ,
# ]
不论方法或属性,都是遵从上述顺序进行搜索调用的。简单总结起来大致有3点:1)子类优先;2)声明顺序优先;3)单调性。
当我们尝试写出一种C3算法无解的继承方式时,Python会报错:
classA:pass
classB(A):pass
classC(A,B):pass
# TypeError: Cannot create a
# consistent method resolution
# order (MRO) for bases A, B
这是因为,按照子类优先原则,的搜索顺序应当位于之前,然而按照声明顺序,却应当在的前面。这样,Python无法给出一个准确的MRO,因而报错。所以,虽然Python支持多重继承,使用不当会导致程序复杂度火箭式上升。
super
在单继承中,可以调用父类的方法:
classA:
defm(self):
print('A.m')
classB(A):
defm(self):
super().m()
print('B.m')
b=B()
b.m()
# A.m
# B.m
如果在多重继承中,调用的又是哪个父类的方法呢?答案是该类MRO的前一个类的方法:
classA:
defm(self):
print('I\'m A')
classB:
defm(self):
print("I'm B")
classC(A):
defm(self):
super().m()
print('I\'m C')
classD(B,A):
defm(self):
print("I'm D")
classE(C,D):
defm(self):
super().m()
print('I\'m E')
classF(D,C):
defm(self):
super().m()
print('I\'m F')
e=E()
f=F()
pprint(E.mro())
# [,
# ,
# ,
# ,
# ,
# ]
e.m()
# I'm D
# I'm C
# I'm E
pprint(F.mro())
# [,
# ,
# ,
# ,
# ,
# ]
f.m()
# I'm D
# I'm F
另外一个关键点在于,调用链会在第一个没有使用的类中断掉。例如上面中没有导致的方法没有被调用。这里引出了我们函数的真正意义所在:保证多重继承的灵活性。试想,当我们实现了一个类希望给别人使用时,其他人很可能为了增加某些功能而混入了其他类,如果我们自己的类中没有使用来初始化父类的话,使用我们类的人只能通过硬编码的方式来编写他们的代码:
# 我们的类
classMy:
def__init__(self):
self.a=1
# 其他人使用
classInject:
def__init__(self):
super().__init__()
self.b=2
classSon(My,Inject):
def__init__(self):
super().__init__()
s=Son()
print(s.b)
# AttributeError:
# 'Son' object has no attribute 'b'
# 其他人只好委屈地修改
classSon(My,Inject):
def__init__(self):
My.__init__(self)
Inject.__init__(self)
s=Son()
print(s.b)
# 2
如果我们自己的类里使用了,那么一切都变得非常简单,其他人不管给出多少个或多少层的混合继承,都只需要一行代码即可完成初始化工作,因为将MRO链上的所有方法串在了一次,大家按照既定的顺序,不多不少都被执行了一次,硬编码问题也不复存在。
classMy:
def__init__(self):
super().__init__()
self.a=1
classInject:
def__init__(self):
super().__init__()
self.b=2
classSon(My,Inject):
def__init__(self):
super().__init__()
s=Son()
print(s.b)
# 2
友情链接: https://vonalex.github.io/
欢迎关注 它不只是Python
领取专属 10元无门槛券
私享最新 技术干货