MRO 多继承
MRO, Method Resolution Order
class A:
def say(self):
print("A")
class M(A):
pass
m = M()
m.say()
输出为:A
class A:
def say(self):
print("A")
class B:
def say(self):
print("B")
class M(B):
pass
m = M()
m.say()
输出为:B
class A:
def say(self):
print("A")
class B:
def say(self):
print("B")
class M(A, B):
pass
m = M()
m.say()
输出为:A
class A:
def say(self):
print("A")
class B:
def say(self):
print("B")
class C(A):
pass
class M(C, B):
pass
m = M()
m.say()
输出为:A
class A:
def say(self):
print("A")
class B(A):
def say(self):
print("B")
class C(A):
pass
class M(C, B):
pass
m = M()
m.say()
输出为:B
怎么从父类里找某个类的优先函数?这就是MRO
两种方式:
M.mro()
M.__mro__
上面最后一个例子打印出来像这样:
[__main__.M, __main__.C, __main__.B, __main__.A, object]
可以看出,在调用M的say()方法时,先在M中找没找到,又在C中找,没找到,最后在B中找,找到了,用的就是B中的say()方法,优先级越来越低
算法
python从2.3版本用的就是C3 MRO(wiki | paper)
C3算法的三个C:(阅读顺序:2->3->1,标号只为了与维基百科一致)
-
a consistent extended precedence graph:把每对类都画上箭头,箭头方向取决于“在这对类的最小公共子类上,这两个类或它们的子类的优先级顺序”(比较难理解,看下面这幅图的解释)
precedence graph类似于下图,指子类和父类之间的关系
解释:观察
scrolling-mixin
和editing-mixin
两个类,第二个C决定了所有scrollable-pane
及其子类中,pane
都优先于scrolling-mixin
,同理所有editable-pane
及其子类中,pane
都优先于editing-mixin
。第三个C保证了在editable-scrollable-pane
中,使用的方法一定是scrollable-pane
或editable-pane
中使用的方法。第一个C确定了scrolling-mixin
和editing-mixin
的顺序:- 找到两者的最小公共子类,即保证这个子类的所有父类都不是两者共同的子类,这个例子中就是
editable-scrollable-pane
- 只要
scrolling-mixin
或scrolling-mixin
的子类排在editing-mixin
或editing-mixin
的子类之前,则scrolling-mixin
优先级就高于editing-mixin
,这个例子中,由于scrollable-pane
排在editable-pane
之前,所以scrolling-mixin
优先级高于editing-mixin
- 找到两者的最小公共子类,即保证这个子类的所有父类都不是两者共同的子类,这个例子中就是
-
preservation of local precedence order(局部的优先顺序):当一个class继承了多个class时,会优先使用写在前面的class中的方法;同时,这个class的所有subclass(子类)也同样保持这个特性。比如M继承了(A,B)(注意顺序),C继承了M,对C调用say()方法的时候A的优先级也比B高
比如:
class A: def say(self): print("A") class B: def say(self): print("B") class M(A, B): pass m = M() m.say()
输出A,然而:
class A: def say(self): print("A") class B: def say(self): print("B") class M(B, A): pass m = M() m.say()
输出B
-
fitting a monotonicity criterion(符合单调性标准):任何一个class使用的方法必须来自其直接父类使用的方法,这一部分比较复杂,大家可以去b站看码农高天的视频,很清晰的。这是论文中提到的一个例子
假设有一个方法只存在于day-boat和wheel-boat上,可以看到,pedalo的两个父类:pedal-wheel-boat和small-catamaran在继承时优先使用day-boat的方法(由它们的MRO得来),而pedalo作为它们的子类,优先使用的是wheel-boat的方法,它没有使用父类使用的方法,或者说它跳过了父类,使用了其他类的方法,这就破坏了单调性标准
不满足3C算法的类定义
class A:
def say(self):
print("A")
class B:
def say(self):
print("B")
# 由第二个C知道,对class C来说,A在B前面
class C(A, B):
pass
# 由第二个C知道,对class D来说,B在A前面
class D(B, A):
pass
# 由第一个C知道,由于C排在D之前,那说明A应该排在B之前
# 但class D规定B排在A前面,产生矛盾
class M(C, D):
pass
继承关系如下图所示:
结语
多继承在python中是被允许的语法,在java中则取消了多继承。MRO最基本需要掌握在写完代码之后怎么通过mro()方法或__mro__
成员判断调用方法时候的优先级。文中提到的up主码农高天在讲解python一些比较细致和深入的问题上很清晰,感兴趣的小伙伴可以关注关注。如果有任何疑问或错误欢迎在评论区里提出,我们一起讨论~