MRO

对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就需要对当前类和基类进行搜索以确定方法

所在的位置。而搜索的顺序就是所谓的「方法解析顺序」(Method Resolution Order,或MRO)。对于只支持单继承的语言来说,MRO 一般比较简单;

而对于 Python 这种支持多继承的语言来说,MRO 就复杂很多。

先看一个「菱形继承」的例子:

如果 x 是 D 的一个实例,那么 x.show() 到底会调用哪个 show 方法呢?

    1)如果按照 [D, B, A, C] 的搜索顺序,那么 x.show() 会调用 A.show();

    2)如果按照 [D, B, C, A] 的搜索顺序,那么 x.show() 会调用 C.show()。

由此可见,MRO 是把类的继承关系线性化的一个过程,而线性化方式决定了程序运行过程中具体会调用哪个方法。

既然如此,那什么样的 MRO 才是最合理的?Python 中又是如何实现的呢?

Python 至少有三种不同的 MRO:

    1)经典类(classic class)的深度遍历。

    2)Python 2.2 的新式类(new-style class)预计算。

    3)Python 2.3 的新式类的 C3 算法。它也是 Python 3 唯一支持的方式。

1. 经典类中的 MRO

   Python 有两种类:经典类(classic class)和新式类(new-style class)。两者的不同之处在于新式类继承自 object。

       1)在 Python 2.1 以前,经典类是唯一可用的形式;

       2)Python 2.2 引入了新式类,使得类和内置类型更加统一;

       3)在 Python 3 中,新式类是唯一支持的类。

   经典类采用了一种很简单的 MRO 方法:从左至右的深度优先遍历。以上述「菱形继承」为例,其查找顺序为 [D, B, A, C, A],如果只保留重复

   类的第一个则结果为 [D,B,A,C]。我们可以用 inspect.getmro(class) 来获取类的 MRO。

class A:
    def show(self):
        print("A.show()")

class B(A):
    pass

class C(A):
    def show(self):
        print("C.show()")

class D(B, C):
    pass

x = D()
x.show()

   这种深度优先遍历对于简单的情况还能处理的不错,但是对于上述「菱形继承」其结果却不尽如人意:虽然 C.show() 是 A.show() 的更具体化版

   本(显示了更多的信息),但我们的x.show() 没有调用它,而是调用了 A.show()。这显然不是我们希望的结果。对于新式类而言,所有的类都继

   承自 object,所以「菱形继承」是非常普遍的现象,因此不可能采用这种 MRO 方式。

2. Python 2.2中的新式类 MRO

   为解决经典类 MRO 所存在的问题,Python 2.2 针对新式类提出了一种新的 MRO 计算方式:在定义类时就计算出该类的 MRO 并将其作为类的属性。

   因此新式类可以直接通过 __mro__ 属性获取类的 MRO。

   对于新式类的 MRO,将是自左向右的广度遍历,上述钻石继承的顺序就变成了 [D,B,C,A,object],解决了菱形继承(下面左图)在经典类中存在的问题。

   但是对于正常的继承关系(如下面右图),根据新式类中的广度遍历原则,查找顺序为[E,C,D,A,B,object],A 是 C 的唯一基类,但却在 C 之后先查询 D,

   根据单调性,应该先从唯一基类进行查找。

   注意:在 Python 2.2 到 Python3 之间的版本仍然存在经典类,在定义类时:

         1)继承 object 才是新式类,MRO 按从左向右的深度优先原则;

         2)没有继承 object 就是经典类,MRO 按从左向右的广度优先原则。

        

   对比一下,左侧为 Python 2 中的经典类运行结果,右侧为 Python 2 中的新式类运行结果:

            

3. Python 2.3及其以后的新式类 MRO

   新式类的 MRO 使用 C3 算法,并且在Python 3中只存在新式类。Python 2.3及其以后的新式类 MRO,使用的是拓扑排序,在一个有向无环图中:

       1)从左到右选择一个入度为0的顶点并输出(入度:以某顶点为弧头,终止于该顶点的弧的数目)

       2)从网中删除此顶点以及所有出边

       3)重复步骤1、2,直到所有点都被遍历

   举个几个例子:

           

      ----------------------------------------------------------------------------------------------------------

      

      ----------------------------------------------------------------------------------------------------------

                     

原文地址:https://www.cnblogs.com/yanghh/p/13224397.html