为什么会讲 MRO?
- 在讲多继承的时候:https://www.cnblogs.com/poloyy/p/15224912.html
- 有讲到, 当继承的多个父类拥有同名属性、方法,子类对象调用该属性、方法时会调用哪个父类的属性、方法呢?
- 这就取决于 Python 的 MRO 了
什么是 MRO
- MRO,method resolution order,方法搜索顺序
- 对于单继承来说,MRO 很简单,从当前类开始,逐个搜索它的父类有没有对应的属性、方法
- 所以 MRO 更多用在多继承时判断方法、属性的调用路径
- Python 中针对类提供了一个内置属性 __mro__ 可以查看方法搜索顺序
实际代码
class A: def test(self): print("AAA-test") class B: def test(self): print("BBB-test") # 继承了三个类,B、A、还有默认继承的 object class C(B, A): ... # 通过类对象调用,不是实例对象! print(C.__mro__) # 输出结果 (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
- 在搜索方法时,是按照 __mro__ 的输出结果从左往右的顺序查找的
- 如果在当前类(Class C)中找到方法,就直接执行,不再搜索
- 如果没有找到,就查找下一个类中(Class B)是否有对应的方法,如果找到,就直接执行,不再搜素
- 如果找到最后一个类(Class object)都没有找到方法,程序报错
类图
注意
其实 MRO 是涉及一个底层算法的,下面来详细讲解一下
MRO 算法
Python 发展到现在经历了三种算法
- 旧式类 MRO 算法:从左往右,采用深度优先搜索(DFS),从左往右的算法,称为旧式类的 MRO
- 新式类 MRO 算法:自 Python 2.2 版本开始,新式类在采用深度优先搜索算法的基础上,对其做了优化
- C3 算法:自 Python 2.3 版本,对新式类采用了 C3 算法;由于 Python 3.x 仅支持新式类,所以该版本只使用 C3 算法
什么是旧式类,新式类
https://www.cnblogs.com/poloyy/p/15226425.html
想深入了解 C3 算法的可以看看官网
https://www.python.org/download/releases/2.3/mro/
旧式类 MRO 算法
需要在 python2 环境下运行这段代码
实际代码
# 旧式类算法 class A: def test(self): print("CommonA") class B(A): pass class C(A): def test(self): print("CommonC") class D(B, C): pass D().test() # python2 下的运行结果 CommonA
类图
分析
- 通过类图可以看到,此程序中的 4 个类是一个“菱形”继承的关系
- 当使用 D 类实例对象访问 test() 方法时,根据深度优先算法,搜索顺序为 D->B->A->C->A
- 因此,旧式类 MRO 算法最先搜索得到 test() 方法是在 A 类里面,所以最终输出结果为 CommonA
新式类 MRO 算法
- 为解决旧式类 MRO 算法存在的问题,Python 2.2 版本推出了新的计算新式类 MRO 的方法
- 它仍然采用从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个
以上面的代码栗子来讲
- 深度优先遍历,搜索顺序为 D->B->A->C->A
- 因为顺序中有 2 个 A,因此只保留最后一个
- 最终搜索顺序为 D->B->C->A
新式 MRO 算法的问题
虽然解决了旧式 MRO 算法的问题,但可能会违反单调性原则
什么是单调性原则?
在子类存在多继承时,子类不能改变父类的 MRO 搜索顺序,否则会导致程序发生异常
实际代码
class X(object): pass class Y(object): pass class A(X, Y): pass class B(Y, X): pass class C(A, B): pass
- 深度优先遍历后的搜索顺序为: C->A->X->object->Y->object->B->Y->object->X->object
- 相同取后者的搜索顺序为: C->A->B->Y->X->object