Python的程序结构[2] -> 类/Class[2] -> 方法解析顺序 MRO
方法解析顺序 / MRO (Method Resolution Order)
关于方法解析顺序(MRO)的详细内容可以参考文末链接,这里主要对 MRO 进行简要的总结说明以及一些练习示例。
经典类和新式类的 MRO
经典类
描述: 一种不能继承的类,如果经典类为父类,其子类调用父类的构造函数时会报错。且不具备 __mro__ 属性。
MRO: Deepth First Search (DFS) / 深度优先搜索
新式类
描述: 新式类是一种为了解决经典类中只能继承不能重写的问题而引入的,新式类默认继承自 object,且子类可以调用父类构造函数,在 Python 之后均为新式类。
MRO: C3算法
下面的代码以 Python2.7 和 Python3.4 分别运行将产生不一样的结果,这是由于在 Python3 中默认使用了新式类,因此采用了 C3 搜索,而 Python2 中默认为经典类,采用 DFS 搜索,因此产生了不同,若将 A 的基类指定为 object 则输出将变为相同。
"""
A
/\
/ \
B C
\ /
\/
D
"""
from distutils.log import warn as printf class A:
def __init__(self):
pass def show(self):
printf("This is A") class B(A): pass class C(A):
def show(self):
printf("This is C") class D(B, C): pass d = D()
d.show()
# Python2 --> This is A
# Python3 --> This is C
DFS / BFS / C3
主要以正常继承和菱形继承为例分别说明三种算法的缺陷和优势。
DFS深度优先搜索
利用深度优先搜索,对于正常继承可以很好的完成搜索,但当出现菱形继承时,则会出现搜索的缺陷,在菱形继承中 MRO 的顺序为 ABDC,若此时 C 重写了 D 的方法,可由于继承顺序的问题,将导致优先搜索到 D 的方法,也就是说,C 的重写是无效的,C 对于 D 只能继承,不能重写。

BFS广度优先搜索
广度优先搜索则恰好和深度优先搜索相反,在菱形继承中能够很好的完成搜索,而在普通搜索中却出现了一定缺陷,普通搜索的顺序为ABCDE,但这却违反了单调性原则,即B和C是两个互不相关的父类,在B搜索结束后应该优先搜索D而非C。

C3算法
基于上述的两种算法缺陷,Python2.3之后的新式类中MRO全都采用了C3算法,这种算法解决了DFS的只能继承无法重写的问题和BFS的单调性问题。

Merge List公式
C3 算法的继承模式主要采用了 merge list 公式,首先假设类的线性化 MRO 记为 L[C]=[C1, C2, C3],则称 C1 为 L[C] 的头,其余为尾。若 C 继承自 B1,B2…,则可以根据以下公式计算出 L[C],
L[object] = [object]
L[C(B1…BN)] = [C] + merge(L[B1]…L[BN], [B1]…[BN])
整个计算公式的关键在于 merge,输入为一组列表,输出为一个列表,输出方式为,
1. 检查第一个列表的头元素(如 L[B1] 的头),记为 H;
2. 若 H 未出现在其他列表的尾部,则输出该元素,并删除所有列表中的 H,然后重复步骤 1。若 H 出现在其他列表的尾部,则取下一个列表的头部继续步骤。
3. 重复步骤直至列表为空,则算法结束,若列表不为空且无法输出元素,则说明无法构建继承关系,此时抛出异常。
下面分别举例来构建一个异常的继承关系和正常的继承进行对比,
异常继承
"""
________
/ \
X Y |
\ /\ /
\/ \ /
A B
\ /
\/
C
""" class X(object): pass class Y(object): pass class A(X, Y): pass class B(Y, X): pass class C(A, B): pass print(C.__mro__)
这段代码最终会报错,
Traceback (most recent call last):
File "C:\Users\EKELIKE\Documents\Python Note\3_Program_Structure\3.3_Class\mro_c3_error.py", line 21, in <module>
class C(A, B): pass
TypeError: Cannot create a consistent method resolution
order (MRO) for bases X, Y
原因在于 merge 公式计算到最后无法输出参数,即构建继承失败,计算过程如下,
C3 Algothrim Calculation:
L[object] = [object]
L[X] = [X] + merge(L[object]) = [X, object]
L[Y] = [Y] + merge(L[object]) = [Y, object]
L[A] = [A] + merge(L[X], L[Y], [X], [Y]) = [A, X, Y, object]
L[B] = [B] + merge(L[Y], L[X], [Y], [X]) = [B, Y, X, object]
L[C] = [C] + merge(L[A], L[B], [A], [B])
= [C] + merge([A, X, Y, object], [B, Y, X, object], [A], [B])
= [C, A, B] + merge([X, Y, object], [Y, X, objcet])
--> Raise Error
正常继承
下面给出几个正常继承的示例及其代码,
# C3 MRO
# ----------------------
"""
Object
/ | \
/ | \
X _ Y Z
\ \/ /
\ /\ /
A B
\/
C
""" class X(object): pass class Y(object): pass class Z(object): pass class A(X, Y): pass class B(X, Z): pass class C(A, B): pass print(C.__mro__)
# ----------------------
# ----------------------
"""
Object
/ | \
/ | \
D E F
\ / \ /
\ / \/
J K
\ /
L
""" class D(object): pass class E(object): pass class F(object): pass class J(D, E): pass class K(E, F): pass class L(J, K): pass print(L.__mro__)
其中第二种继承的MRO计算公式如下,
C3 Algorithm Calculation:
L[object] = [object]
L[D] = [D] + merge(L[object]) = [D, object]
L[E] = [E] + merge(L[object]) = [E, object]
L[F] = [F] + merge(L[object]) = [F, object]
L[J] = [J] + merge(L[D], L[E], [D], [E]) = [J, D, E, object]
L[K] = [K] + merge(L[E], L[F], [E], [F]) = [K, E, F, object]
L[L] = [L] + merge(L[J], L[K], [J], [K])
= [L, J] + merge([D, E, object], [K, E, F, object], [K])
= [L, J, D] + merge([E, object], [K, E, F, object], [K])
= [L, J, D, K] + merge([E, object], [E, F, object])
= [L, J, D, K, E] + merge([object], [F, object])
= [L, J, D, K, E, F] + merge([object], [object])
= [L, J, D, K, E, F, object]
查看MRO
当然,在使用时我们不必要每次都对类继承顺序进行计算,在 Python 中可以使用以下两种方式来查看一个类的 MRO。
import inspect # Diamond inherit
class A: pass class B(A): pass class C(A): pass class D(B, C): pass print(inspect.getmro(D))
print(D.__mro__)
最终得到输出的结果如下,这一结果即是通过 MRO C3 算法进行计算得出的。
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
参考链接
http://ju.outofmemory.cn/entry/137896
http://blog.csdn.net/lis_12/article/details/52859376
Python的程序结构[2] -> 类/Class[2] -> 方法解析顺序 MRO的更多相关文章
- Python的方法解析顺序(MRO)[转]
本文转载自: http://hanjianwei.com/2013/07/25/python-mro/ 对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就 ...
- sqlalchemy mark-deleted 和 python 多继承下的方法解析顺序 MRO
sqlalchemy mark-deleted 和 python 多继承下的方法解析顺序 MRO 今天在弄一个 sqlalchemy 的数据库基类的时候,遇到了跟多继承相关的一个小问题,因此顺便看了一 ...
- python 方法解析顺序 mro
一.概要: mor(Method Resolution Order),即方法解析顺序,是python中用于处理二义性问题的算法 二义性: 1.两个基类,A和B都定义了f()方法,c继承A和B那么C调用 ...
- Python的程序结构[2] -> 类/Class[0] -> 类的特殊属性
类的特殊属性 / Special Property of Class Python 中通过 class 进行类的定义,类可以实例化成实例并利用实例对方法进行调用. 类中还包含的一些共有的特殊属性. 特 ...
- Python的程序结构[2] -> 类/Class[1] -> 基类与继承
基类与继承 / Base Class and Inheritance Class 面向对象的特性使得 Python 中不可避免地需要使用到类和类的继承,类的继承可以使得代码很好的被重用.下面以一些代码 ...
- Python的程序结构[2] -> 类/Class[3] -> 内建类与内建函数
内建类与内建函数的区分 / Distinction of Built-in Type and Function 对于 Python,有许多可以不需要定义或引用就可以使用的函数(类)(参考内建模块),诸 ...
- Python的程序结构[2] -> 类/Class[5] -> 内建类 bytes 和 bytearray
内建类 bytes 和 bytearray / Built-in Type bytes and bytearray 关于内建类 Python的内建类 bytes 主要有以下几点: class byte ...
- Python的程序结构[2] -> 类/Class[4] -> 内建类 super
内建类 super / Built-in Type super 关于内建类 对于 super 可以从官方文档中看到基本介绍,super 接收一个类,以及类或类的实例,最终返回一个代理对象的实例.而 M ...
- Python的程序结构[2] -> 类/Class[6] -> 内建类 map
内建类map / Built-in Type map 关于内建类 map 是一个内建的类,能够返回一个 map 的 obj.map 的第一个参数为一个可执行函数,后续参数均为可迭代对象,map 会分别 ...
随机推荐
- thinkphp3.1.3验证码优化
现状 thinkphp3.1.3版本中的验证码字符分布不均匀,在自定义宽高时很明显. 调用代码: Image::buildImageVerify(6, 5, 'png', 150, 50); 生成的验 ...
- katalon系列一:初识Katalon Studio自动化测试工具
最近准备把公司的系统搞上UI自动化,先是自己用Python+selenium+pytest写了一个框架,开始写case的时候发现效率极其慢.原因为: (1)开发为提高前端响应时间,使用前端路由技术,一 ...
- Spring Boot多数据源配置(二)MongoDB
在Spring Boot多数据源配置(一)durid.mysql.jpa 整合中已经讲过了Spring Boot如何配置mysql多数据源.本篇文章讲一下Spring Boot如何配置mongoDB多 ...
- 孤荷凌寒自学python第十天序列之字符串的常用方法
孤荷凌寒自学python第十天序列之字符串的常用方法 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) Python的字符串操作方法非常丰富,原生支持字符串的多种操作: 1 查找子字符串 str ...
- php数组循环的三种方式
PHP 的遍历数组的三种方式:for循环.foreach循环.while.list().each()组合循环 PHP当中数组分为:索引数组[转换成json是数组]和关联数组[转换成json是对象] f ...
- .NET Core Linux 部署实践
部署环境:CentOS 7 x64 必要条件:当前用户有sudo权限 1. 安装依赖包sudo yum install libunwind libicu2. 下载.net core安装文件curl - ...
- Spring 学习笔记(六)—— AOP的简单理解
系统中的业务可以分为核心关注点和横切关注点. 核心关注点时业务处理的主要流程,而横切关注点是与核心业务无关但更为通用的业务. 各个横切关注点离散地穿插于核心业务之中,导致系统地每一个模块都与这些业务具 ...
- 再看数据库——(5)Group By与Order By
在使用sql语句时,很多人都会分不清order by与group by,其实简单的说: order by -- 排序 group by --分组 1.order by是行的排序,默认为升序. 有两种方 ...
- 【noip2016d2t3】状压DP+巧妙优化
题意可以简单这样考虑 给出n^2个集合(每个集合的元素不超过n),包含某个元素的集合至少有n个,选出最少的集合,使这些集合的并包含n个元素 n最大只有18 可以考虑状压n个元素,然后枚举n^2个集合 ...
- BZOJ3597 [Scoi2014]方伯伯运椰子 【二分 + 判负环】
题目链接 BZOJ3597 题解 orz一眼过去一点思路都没有 既然是流量网络,就要借鉴网络流的思想了 我们先处理一下那个比值,显然是一个分数规划,我们二分一个\(\lambda = \frac{X ...