Python - 面向对象编程 - MRO 方法搜索顺序
为什么会讲 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
分析不同类的 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 中运行
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() # 输出结果
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 的运算方式
- 检查 merge 列表的头元素(如 L[A] 的头),记作 H
- 若 H 未出现在 merge 表达式中其他列表的尾部,则将其输出,并将其从所有列表中删除
- 然后回到步骤一,取出下一个列表的头元素记作 H
重复以上步骤直到列表为空,则算法结束;如果不能再找出可以输出的元素,则抛出异常
简单的类计算 MRO
class B(object): pass print(B.__mro__) (<class '__main__.B'>, <class 'object'>)
MRO 计算方式
L[B] = L[B(object)]
= B + merge(L[object])
= B + L[object]
= B object
单继承的类计算 MRO
# 计算 MRO
class B(object): pass class C(B): pass print(C.__mro__) (<class '__main__.C'>, <class '__main__.B'>, <class 'object'>)
MRO 计算方式
L[C] = C + merge(L[B])
= C + L[B]
= C B object
多继承的类计算 MRO
O = object class F(O): pass class E(O): pass class D(O): pass class C(D, F): pass class B(D, E): pass class A(B, C): pass print(C.__mro__)
print(B.__mro__)
print(A.__mro__) # 输出结果
(<class '__main__.C'>, <class '__main__.D'>, <class '__main__.F'>, <class 'object'>)
(<class '__main__.B'>, <class '__main__.D'>, <class '__main__.E'>, <class 'object'>)
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.F'>, <class 'object'>)
O 类、object 类 MRO 计算
L[O] = O = object
D、E、F 类 MRO 计算
L[D] = D + merge(L[O])
= D O
C 类 MRO 计算
L[C] = L[C(D, F)]
= C + merge(L[D], L[F], DF)
# 从前面可知 L[D] 和 L[F] 的结果
= C + merge(DO, FO, DF)
# 因为 D 是顺序第一个并且在几个包含 D 的 list 中是 head,
# 所以这一次取 D 同时从列表中删除 D
= C + D + merge(O, FO, F)
# 因为 O 虽然是顺序第一个但在其他 list (FO)中是在尾部, 跳过
# 改为检查第二个list FO
# F 是第二个 list 和其他 list 的 head
# 取 F 同时从列表中删除 F
= C + D + F + merge(O)
= C D F O
B 类 MRO 计算
L[B] = L[B(D, E)]
= B + merge(L[D], L[E], DE)
= B + merge(DO, EO, DE)
= B + D + merge(O, EO, E)
= B + D + E + merge(O)
= B D E O
A 类 MRO 计算
L[A] = L[A(B,C)]
= A + merge(L[B], L[C], BC)
= A + merge( BDEO, CDFO, BC )
= A + B + merge( DEO, CDFO, C )
# D 在其他列表 CDFO 不是 head,所以跳过到下一个列表的 头元素 C
= A + B + C + merge( DEO, DFO )
= A + B + C + D + merge( EO, FO )
= A + B + C + D + E + merge( O, FO )
= A + B + C + D + E + F + merge( O )
= A B C D E F O
Python - 面向对象编程 - MRO 方法搜索顺序的更多相关文章
- Python中的MRO(方法解析顺序)[转载]
本文转载至: http://hanjianwei.com/2013/07/25/python-mro/ 对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就 ...
- Python - 面向对象编程 - 魔术方法(双下划线方法)
什么是魔术方法 在Python中,所有以 __ 双下划线包起来的方法,都统称为 Magic Method 魔术方法,也叫双下划线方法 有哪些重要的魔术方法? __new__ https://www.c ...
- Python - 面向对象编程 - 子类方法的重写
继承的详解 https://www.cnblogs.com/poloyy/p/15216652.html 方法的重写 在子类继承父类时,子类会拥有父类的所有属性和方法 但当父类的方法实现不满足子类需要 ...
- python学习【第九篇】python面向对象编程
一.面向对象了解 面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. Pyth ...
- Python - 面向对象编程 - 多继承
继承的详解 https://www.cnblogs.com/poloyy/p/15216652.html 这篇文章讲的都是单继承,Python 中还有多继承 Python 多继承的背景 大部分面向对象 ...
- Python - 面向对象编程 - super()
前置知识 继承的详解:https://www.cnblogs.com/poloyy/p/15216652.html 子类方法的重写:https://www.cnblogs.com/poloyy/p/1 ...
- Python面向对象编程——继承与派生
Python面向对象编程--继承与派生 一.初始继承 1.什么是继承 继承指的是类与类之间的关系,是一种什么"是"什么的关系,继承的功能之一就是用来解决代码重用问题. 继承是一种创 ...
- 图解python | 面向对象编程
作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/56 本文地址:http://www.showmeai.tech/article-det ...
- Python 面向对象(二) 特殊方法
一些Python特殊方法的汇总 __bases__ 类的基类,返回元祖__base__ 类的基类,也叫父类__call__ '类名()',类名加括号调用时执行的语句__class__ 对象或类 ...
随机推荐
- iloc与loc总结
pandas中操作DataFrame时候,经常用到**loc,iloc,at,iat,ix** loc函数通过行索引"index"中具体的值来去行的数据(如取"index ...
- 工业互联网之微信小程序整体解决方案总结
随着工业互联网的快速发展,以及微信小程序的成熟,将两者结合实现对工况数据的查看和存储方案变得简单.以下方式为本人尝试过的整体解决方案,可以顺利实现无缝对接. 1.采集方式 1.ifix/intouch ...
- 嵌套div的onClick事件问题
嵌套div的onClick事件问题我在下面的代码中的外层div中加了onClick事件,这样当鼠标点击这个div的时候就会跳转了.但是我在图片上加了一些其他效果,所以当鼠标点击中间的img时不能触发跳 ...
- 重返MySQL之MySQL基础
重返MySQL之MySQL基础 本章详细介绍了,什么是数据库,常见的关系型数据库有哪些,什么是MySQL,及MySQL中DDL操作表,DML操作表记录. 1.0 数据库概述 1.1 数据存储的方式 目 ...
- Maven项目思考&实战
参考了网络上很多文章, 特此感谢. Maven项目规范 同一项目中所有模块版本保持一致 子模块统一继承父模块的版本 统一在顶层模块Pom的节中定义所有子模块的依赖版本号,子模块中添加依赖时不要添加版本 ...
- 关于在iar+j-link上的坑坑洼洼
引言 iar版本为8.32,j-link驱动版本为4.34,对应的是stm32 ARM cortex-m3 ,文末有本文的软件和工具,以及需要的文件的链接(免费) 第一坑:iar注册机不能注册iar9 ...
- OpenFaaS实战之七:java11模板解析
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- 【SpringCloud微服务实战】搭建企业级应用开发框架(一):架构说明
SpringCloud分布式应用微服务系统架构图: SpringCloud分布式应用微服务系统组件列表: 微服务框架组件:Spring Boot2 + SpringCloud Hoxton.SR8 + ...
- springboot 中 inputStream 神秘消失之谜
序言 最近小明接手了前同事的代码,意料之外.情理之中的遇到了坑. 为了避免掉入同一个坑两次,小明决定把这个坑记下来,并在坑前立一个大牌子,避免其他小伙伴掉进去. HTTPClient 模拟调用 为了把 ...
- Windows内核开发-5-(2)-内核模式调试
Windows内核开发-5-(2)-内核模式调试 普通用户模式的调试,采取的是给进程添加一个线程来挂起断点,作为一个调试器的线程在进程中使用.照这样来类推,对操作系统调试相当于添加一个进程来限制操作系 ...