• python3 mro


    我们知道,Python 类是支持(多)继承的,一个类的方法和属性可能定义在当前类,也可能定义在基类。针对这种情况,当调用类方法或类属性时,就需要对当前类以及它的基类进行搜索,以确定方法或属性的位置,而搜索的顺序就称为方法解析顺序。

    方法解析顺序(Method Resolution Order),简称 MRO。对于只支持单继承的编程语言来说,MRO 很简单,就是从当前类开始,逐个搜索它的父类;而对于 Python,它支持多继承,MRO 相对会复杂一些。

    实际上,Python 发展至今,经历了以下 3 种 MRO 算法,分别是:

    1. 从左往右,采用深度优先搜索(DFS)的算法,称为旧式类的 MRO;
    2. 自 Python 2.2 版本开始,新式类在采用深度优先搜索算法的基础上,对其做了优化;
    3. 自 Python 2.3 版本,对新式类采用了 C3 算法。由于 Python 3.x 仅支持新式类,所以该版本只使用 C3 算法。

    有关旧式类和新式类的讲解,可阅读《Python super()使用注意事项》一文。

    有读者可能会好奇,为什么 MRO 弃用了前两种算法,而选择最终的 C3 算法呢?原因很简单,前 2 种算法都存在一定的问题。

    旧式类MRO算法

    在使用旧式类的 MRO 算法时,以下面代码为例(程序一):

    1. class A:
    2. def method(self):
    3. print("CommonA")
    4. class B(A):
    5. pass
    6. class C(A):
    7. def method(self):
    8. print("CommonC")
    9. class D(B, C):
    10. pass
    11. print(D().method())

    通过分析可以想到,此程序中的 4 个类是一个“菱形”继承的关系,当使用 D 类对象访问 method() 方法时,根据深度优先算法,搜索顺序为 D->B->A->C->A

    旧式类的 MRO 可通过使用 inspect 模块中的 getmro(类名) 函数直接获取。例如 inspect.getmro(D) 表示获取 D 类的 MRO。

    因此,使用旧式类的 MRO 算法最先搜索得到的是基类 A 中的 method() 方法,即在 Python 2.x 版本中,此程序的运行结果为:

    CommonA

    但是,这个结果显然不是想要的,我们希望搜索到的是 C 类中的 method() 方法。

    新式类MRO算法

    为解决旧式类 MRO 算法存在的问题,Python 2.2 版本推出了新的计算新式类 MRO 的方法,它仍然采用从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个。

    仍以上面程序为例,通过深度优先遍历,其搜索顺序为 D->B->A->C->A,由于此顺序中有 2 个 A,因此仅保留后一个,简化后得到最终的搜索顺序为 D->B->C->A

    新式类可以直接通过 类名.__mro__ 的方式获取类的 MRO,也可以通过 类名.mro() 的形式,旧式类是没有 __mro__ 属性和 mro() 方法的。

    可以看到,这种 MRO 方式已经能够解决“菱形”继承的问题,但是可能会违反单调性原则。所谓单调性原则,是指在类存在多继承时,子类不能改变基类的 MRO 搜索顺序,否则会导致程序发生异常。

    例如,分析如下程序(程序二):

    1. class X(object):
    2. pass
    3. class Y(object):
    4. pass
    5. class A(X,Y):
    6. pass
    7. class B(Y,X):
    8. pass
    9. class C(A, B):
    10. pass

    通过进行深度遍历,得到搜索顺序为 C->A->X->object->Y->object->B->Y->object->X->object,再进行简化(相同取后者),得到 C->A->B->Y->X->object

    下面来分析这样的搜索顺序是否合理,我们来看下各个类中的 MRO:

    • 对于 A,其搜索顺序为 A->X->Y->object;
    • 对于 B,其搜索顺序为 B->Y->X->object;
    • 对于 C,其搜索顺序为 C->A->B->X->Y->object。

    可以看到,B 和 C 中,X、Y 的搜索顺序是相反的,也就是说,当 B 被继承时,它本身的搜索顺序发生了改变,这违反了单调性原则。

    MRO C3

    为解决 Python 2.2 中 MRO 所存在的问题,Python 2.3 采用了 C3 方法来确定方法解析顺序。多数情况下,如果某人提到 Python 中的 MRO,指的都是 C3 算法。

    在 Python 2.3 及后续版本中,运行程序一,得到如下结果:

    CommonC


    运行程序二,会产生如下异常:

    Traceback (most recent call last):
      File "C:UsersmengmaDesktopdemo.py", line 9, in <module>
        class C(A, B):
    TypeError: Cannot create a consistent method resolution
    order (MRO) for bases X, Y

    由此可见,C3 可以有效解决前面 2 种算法的问题。那么,C3 算法是怎样实现的呢?

    以程序一为主,C3 把各个类的 MRO 记为如下等式:

    • 类 A:L[A] = merge(A , object)
    • 类 B:L[B] = [B] + merge(L[A] , [A])
    • 类 C:L[C] = [C] + merge(L[A] , [A])
    • 类 D:L[D] = [D] + merge(L[A] , L[B] , [A] , [B])

    注意,以类 A 等式为例,其中 merge 包含的 A 称为 L[A] 的头,剩余元素(这里仅有一个 object)称为尾。

    这里的关键在于 merge,它的运算方式如下:

    1. 检查第一个列表的头元素(如 L[A] 的头),记作 H。
    2. 若 H 未出现在 merge 中其它列表的尾部,则将其输出,并将其从所有列表中删除,然后回到步骤 1;否则,取出下一个列表的头部记作 H,继续该步骤。

    重复上述步骤,直至列表为空或者不能再找出可以输出的元素。如果是前一种情况,则算法结束;如果是后一种情况,Python 会抛出异常。

    由此,可以计算出类 B 的 MRO,其计算过程为:

    L[B] = [B] + merge(L[A],[A])
         = [B] + merge([A,object],[A])
         = [B,A] + merge([object])
         = [B,A,object]

    同理,其他类的 MRO 也可以轻松计算得出。这里不再赘述,有兴趣的读者可自行推算。

  • 相关阅读:
    python调用函数
    python递归函数的执行过程
    linux rwx 权限说明
    linux ssh scp免密码
    linux的bash特性
    python3常用的内置函数
    linux清理系统缓存
    vim常用命令
    公司项目安装禅道
    jquery 自定义动画
  • 原文地址:https://www.cnblogs.com/SunshineKimi/p/12446800.html
Copyright © 2020-2023  润新知