从上面的结果可以看到,使用深度优先遍历的查找算法并不合理。因此,Python 3 以及 Python 2 针对 new-style class 采用了新的 MRO 算法。如果你使用 Python 3 重新运行一遍上述脚本,你就可以看到函数 d.who_am_i() 的打印结果是 I am C。
>>> d.who_am_i() I am C >>> D.__mro__ (<class 'test.D'>, <class 'test.B'>, <class 'test.C'>, <class 'test.A'>, <class 'object'>)新算法与基于深度遍历的算法类似,但是不同在于新算法会对深度优先遍历得到的搜索路径进行额外的检查。其从左到右扫描得到的搜索路径,对于每一个节点解释器都会判断该节点是不是好的节点。如果不是好的节点,那么将其从当前的搜索路径中移除。
那么问题在于,什么是一个好的节点?我们说 N 是一个好的节点当且仅当搜索路径中 N 之后的节点都不继承自 N。我们还以上述的类继承图为例,按照深度优先遍历得到类 D 中函数的搜索路径 D, B, A, C, A。之后 Python 解释器从左向右检查时发现第三个节点 A 不是一个好的节点,因为 A 之后的节点 C 继承自 A。因此其将 A 从搜索路径中移除,然后得到最后的调用顺序 D, B, C, A。
采用上述算法,D 中的函数调用将优先查找其直接父类 B 和 C 中的相应函数。
C3线性化算法上一小结我们从直观上概述了针对 new-style class 的 MRO 算法过程。事实上这个算法有一个明确的名字 C3 linearization。下面我们给出其形式化的计算过程。
上面的过程看起来好像很复杂,我们用一个例子来具体执行一下,你就会觉得其实还是挺简单的。假设我们有如下的一个类继承关系:
参考来源:Understanding Python MRO - Class search path
class X(): def who_am_i(self): print("I am a X") class Y(): def who_am_i(self): print("I am a Y") class A(X, Y): def who_am_i(self): print("I am a A") class B(Y, X): def who_am_i(self): print("I am a B") class F(A, B): def who_am_i(self): print("I am a F") Traceback (most recent call last): File "test.py", line 17, in <module> class F(A, B): TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y 为什么采用C3算法C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。
本地优先级:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。
单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。
在Python官网的The Python 2.3 Method Resolution Order中作者举了例子,说明这一情况
F=type('Food', (), {remember2buy:'spam'}) E=type('Eggs', (F,), {remember2buy:'eggs'}) G=type('GoodFood', (F,E), {})根据本地优先级在调用G类对象属性时应该优先查找F类,而在Python2.3之前的算法给出的顺序是G E F O,而在心得C3算法中通过阻止类层次不清晰的声明来解决这一问题,以上声明在C3算法中就是非法的。
小结C3算法的核心 :
遍历执行merge操作的序列,如果一个序列的第一个元素,在其他序列中也是第一个元素,或不在其他序列出现,则从所有执行merge操作序列中删除这个元素,合并到当前的mro中。
merge操作后的序列,继续执行merge操作,直到merge操作的序列为空。
如果merge操作的序列无法为空,则说明不合法。
参考资料理解Python中的多继承-C3 线性化算法
Python的多重继承问题-MRO和C3算法
Deep Thoughts by Raymond Hettinger