为什么会讲 MRO?

  • 在讲多继承的时候:https://www.cnblogs.com/poloyy/p/15224912.html
  • 有讲到, 当继承的多个父类拥有同名属性、方法,子类对象调用该属性、方法时会调用哪个父类的属性、方法呢?
  • 这就取决于 Python 的 MRO 了

什么是 MRO

  • MRO,method resolution order,方法搜索顺序
  • 对于单继承来说,MRO 很简单,从当前类开始,逐个搜索它的父类有没有对应的属性、方法
  • 所以 MRO 更多用在多继承时判断方法、属性的调用路径
  • Python 中针对类提供了一个内置属性 __mro__ 可以查看方法搜索顺序

实际代码

  1. class A:
  2. def test(self):
  3. print("AAA-test")
  4.  
  5. class B:
  6. def test(self):
  7. print("BBB-test")


  8. # 继承了三个类,B、A、还有默认继承的 object
  9. class C(B, A):
  10. ...
  11.  
  12. # 通过类对象调用,不是实例对象!
  13. print(C.__mro__)
  14.  
  15. # 输出结果
  16. (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
  1. 在搜索方法时,是按照 __mro__ 的输出结果从左往右的顺序查找的
  2. 如果在当前类(Class C)中找到方法,就直接执行,不再搜索
  3. 如果没有找到,就查找下一个类中(Class B)是否有对应的方法,如果找到,就直接执行,不再搜素
  4. 如果找到最后一个类(Class object)都没有找到方法,程序报错

类图

注意

其实 MRO 是涉及一个底层算法的,下面来详细讲解一下

MRO 算法

Python 发展到现在经历了三种算法

  1. 旧式类 MRO 算法:从左往右,采用深度优先搜索(DFS),从左往右的算法,称为旧式类的 MRO
  2. 新式类 MRO 算法:自 Python 2.2 版本开始,新式类在采用深度优先搜索算法的基础上,对其做了优化
  3. 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 环境下运行这段代码

实际代码

  1. # 旧式类算法
  2. class A:
  3. def test(self):
  4. print("CommonA")
  5.  
  6. class B(A):
  7. pass
  8.  
  9. class C(A):
  10. def test(self):
  11. print("CommonC")
  12.  
  13. class D(B, C):
  14. pass
  15.  
  16. D().test()
  17.  
  18. # python2 下的运行结果
  19. 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 搜索顺序,否则会导致程序发生异常

实际代码

  1. class X(object):
  2. pass
  3.  
  4. class Y(object):
  5. pass
  6.  
  7. class A(X, Y):
  8. pass
  9.  
  10. class B(Y, X):
  11. pass
  12.  
  13. class C(A, B):
  14. 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: A->Y->X->object
  • C: C->A->B->X->Y->object

很明显,B、C 中间的 X、Y 顺序是相反的,就是说 B 被继承时,它的搜索顺序会被改变,违反了单调性

在 python2 中运行这段代码的报错

在 python3 中运行这段代码的报错

C3 MRO 算法

  • 为解决前面两个算法的问题,Python 2.3 采用了 C3 方法来确定方法搜索顺序
  • 多数情况下,如果别人提到 Python 中的 MRO,指的都是 C3 算法

将上面第一个栗子的代码放到 python3 中运行

  1. class A:
  2. def test(self):
  3. print("CommonA")
  4.  
  5. class B(A):
  6. pass
  7.  
  8. class C(A):
  9. def test(self):
  10. print("CommonC")
  11.  
  12. class D(B, C):
  13. pass
  14.  
  15. D().test()
  16.  
  17. # 输出结果
  18. CommonC

简单了解下 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[B] , L[C] , B , C)

了解一下:头、尾

以 A 类为栗,merge() 包含的 A 成为 L[A] 的头,剩余元素(这里只有 object)称为尾

merge 的运算方式

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

重复以上步骤直到列表为空,则算法结束;如果不能再找出可以输出的元素,则抛出异常

简单的类计算 MRO

  1. class B(object): pass
  2.  
  3. print(B.__mro__)
  4.  
  5. (<class '__main__.B'>, <class 'object'>)

MRO 计算方式

  1. L[B] = L[B(object)]
  2. = B + merge(L[object])
  3. = B + L[object]
  4. = B object

单继承的类计算 MRO

  1. # 计算 MRO
  2. class B(object): pass
  3.  
  4. class C(B): pass
  5.  
  6. print(C.__mro__)
  7.  
  8. (<class '__main__.C'>, <class '__main__.B'>, <class 'object'>)

MRO 计算方式

  1. L[C] = C + merge(L[B])
  2. = C + L[B]
  3. = C B object

多继承的类计算 MRO

  1. O = object
  2.  
  3. class F(O): pass
  4.  
  5. class E(O): pass
  6.  
  7. class D(O): pass
  8.  
  9. class C(D, F): pass
  10.  
  11. class B(D, E): pass
  12.  
  13. class A(B, C): pass
  14.  
  15. print(C.__mro__)
  16. print(B.__mro__)
  17. print(A.__mro__)
  18.  
  19. # 输出结果
  20. (<class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>)
  21. (<class '__main__.B'>, <class '__main__.D'>, <class '__main__.E'>, <class 'object'>)
  22. (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>)

O 类、object 类 MRO 计算

  1. L[O] = O = object

D、E、F 类 MRO 计算

  1. L[D] = D + merge(L[O])
  2. = D O

C 类 MRO 计算

  1. L[C] = L[C(D, F)]
  2. = C + merge(L[D], L[F], DF)
  3. # 从前面可知 L[D] 和 L[F] 的结果
  4. = C + merge(DO, FO, DF)
  5. # 因为 D 是顺序第一个并且在几个包含 D 的 list 中是 head,
  6. # 所以这一次取 D 同时从列表中删除 D
  7. = C + D + merge(O, FO, F)
  8. # 因为 O 虽然是顺序第一个但在其他 list (FO)中是在尾部, 跳过
  9. # 改为检查第二个list FO
  10. # F 是第二个 list 和其他 list 的 head
  11. # 取 F 同时从列表中删除 F
  12. = C + D + F + merge(O)
  13. = C D F O

B 类 MRO 计算

  1. L[B] = L[B(D, E)]
  2. = B + merge(L[D], L[E], DE)
  3. = B + merge(DO, EO, DE)
  4. = B + D + merge(O, EO, E)
  5. = B + D + E + merge(O)
  6. = B D E O

A 类 MRO 计算

  1. L[A] = L[A(B,C)]
  2. = A + merge(L[B], L[C], BC)
  3. = A + merge( BDEO, CDFO, BC )
  4. = A + B + merge( DEO, CDFO, C )
  5. # D 在其他列表 CDFO 不是 head,所以跳过到下一个列表的 头元素 C
  6. = A + B + C + merge( DEO, DFO )
  7. = A + B + C + D + merge( EO, FO )
  8. = A + B + C + D + E + merge( O, FO )
  9. = A + B + C + D + E + F + merge( O )
  10. = A B C D E F O

Python - 面向对象编程 - MRO 方法搜索顺序的更多相关文章

  1. Python中的MRO(方法解析顺序)[转载]

    本文转载至: http://hanjianwei.com/2013/07/25/python-mro/ 对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就 ...

  2. Python - 面向对象编程 - 魔术方法(双下划线方法)

    什么是魔术方法 在Python中,所有以 __ 双下划线包起来的方法,都统称为 Magic Method 魔术方法,也叫双下划线方法 有哪些重要的魔术方法? __new__ https://www.c ...

  3. Python - 面向对象编程 - 子类方法的重写

    继承的详解 https://www.cnblogs.com/poloyy/p/15216652.html 方法的重写 在子类继承父类时,子类会拥有父类的所有属性和方法 但当父类的方法实现不满足子类需要 ...

  4. python学习【第九篇】python面向对象编程

    一.面向对象了解 面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. Pyth ...

  5. Python - 面向对象编程 - 多继承

    继承的详解 https://www.cnblogs.com/poloyy/p/15216652.html 这篇文章讲的都是单继承,Python 中还有多继承 Python 多继承的背景 大部分面向对象 ...

  6. Python - 面向对象编程 - super()

    前置知识 继承的详解:https://www.cnblogs.com/poloyy/p/15216652.html 子类方法的重写:https://www.cnblogs.com/poloyy/p/1 ...

  7. Python面向对象编程——继承与派生

    Python面向对象编程--继承与派生 一.初始继承 1.什么是继承 继承指的是类与类之间的关系,是一种什么"是"什么的关系,继承的功能之一就是用来解决代码重用问题. 继承是一种创 ...

  8. 图解python | 面向对象编程

    作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/56 本文地址:http://www.showmeai.tech/article-det ...

  9. Python 面向对象(二) 特殊方法

    一些Python特殊方法的汇总 __bases__    类的基类,返回元祖__base__  类的基类,也叫父类__call__ '类名()',类名加括号调用时执行的语句__class__ 对象或类 ...

随机推荐

  1. Django模板中变量的运算

    在django中的模板下我们知道变量使用{{xxx}}来呈现,可是当出现两个变量进行运算怎么处理那? #加法: {{value|add:value2}} #返回的结果是value+value2的值,假 ...

  2. Java面向对象10——方法重写

    方法重写 static :  ​ ​ package oop.demon01.demon05; ​ public class Application {     public static void ...

  3. 从理发店小弟到阿里P10大牛,一位高中学渣的“登天”之路

    蚂蚁金服,可能是众多程序猿眼中梦寐以求的归宿,无数拿过数不清奖状的各个高校走出的学子精英都挤破头皮,只为能在蚂蚁占有一席之地. 蚂蚁金服里不乏各种深藏不露的大佬,到了那里才会深刻明白一山还有一山高这句 ...

  4. Alibaba-技术专区-Dubbo3总体技术体系介绍及技术指南(序章)

    Dubbo的背景介绍 Apache Dubbo 是一款微服务开发框架(是一款高性能.轻量级的开源 Java 服务框架),它提供了 RPC通信 与 微服务治理 两大关键能力.这意味着,使用 Dubbo ...

  5. Setup a Simple HTTP Proxy Server

    The host 10.21.3.69 has no H3C client, so it can't access the internet. With Tinyproxy, we can setuu ...

  6. 修改Linux系统的默认语言编码集

    RedHat 今天晚上发现服务器上vi的界面提示变成了乱码,只能将XShell的编码改为GBK才能正常显示,导致consolas字体无法使用,GBK编码下的字体丑陋无比,无法忍受,一轮google之后 ...

  7. awk-07-IO和printf语句

    IO语句 1.getline 2.getline var 把a文件的行,追加到b文件的结尾 把 a 文件的行替换 b 文件的指定字段 把 a 文件的行替换 b 文件的对应字段 3.command | ...

  8. 【笔记】模型泛化与岭回归与LASSO

    模型泛化与岭回归与LASSO 模型正则化 模型正则化,简单来说就是限制参数大小 模型正则化是用什么思路来解决先前过拟合的由于过于拟合导致的曲线抖动(线性方程前的系数都很大) 线性回归的目标就是求一个最 ...

  9. 【笔记】偏差方差权衡 Bias Variance Trade off

    偏差方差权衡 Bias Variance Trade off 什么叫偏差,什么叫方差 根据下图来说 偏差可以看作为左下角的图片,意思就是目标为红点,但是没有一个命中,所有的点都偏离了 方差可以看作为右 ...

  10. 当任意文件上传偶遇Safedog

    0x01 写在前面 渗透过程中可能会经常遭遇WAF,此时不要轻易放弃,绞尽脑汁竭尽全力,或许弹尽粮绝之时也是柳暗花明之日. 0x02 过狗上传 一次项目渗透过程中,找个一处上传功能 先上传图片,测试上 ...