MRO

MRO 全称方法解析顺序(Method Resolution Order),在多重继承和多继承存在的时候,寻找属性及方法的顺序。

深度优先(DFS)与广度优先(BFS)

python2 所用的 mro 就是深度优先的算法,但是深度优先针对菱形继承会有问题,如图:

graph TB
D(D) -->B(B)
D(D) -->C(C)
B(B) -->A(A)
C(C) -->A(A)

DFS: A->B->D->C

BFS:A->B->C->D

如果使用深度优先的算法,C重载了D的一个方法,会导致搜索不到C的重载,只会用到D

那么针对这种菱形继承应该使用BFS。


然而BFS 同样也会具有问题,如图:

graph TB
D(D) -->B(B)
E(E) -->C(C)
B(B) -->A(A)
C(C) -->A(A)

DFS: A->B->D->C->E

BFS: A->B->C->D->E

针对这种继承如果使用广度优先,C和D有同名方法,正常应该使用D的方法(D,B应为一个整体,B的优先级比C高),但是如果广度优先就会使用到C的方法。

C3 linearization 测试

为了解决以上问题 python3 使用的mro是 c3 linearization 算法,翻译就是 c3线性化算法,也就是本文重点介绍的内容。

可以简单看一下 python3 针对上述俩种继承的解析顺序:

# 菱形继承
class D:
pass class B(D):
pass class C(D):
pass class A(B,C):
pass print(A.__mro__)

输出:

(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
class D:
pass class E:
pass class B(D):
pass class C(E):
pass class A(B,C):
pass print(A.__mro__)

输出:

(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)

可以看到,顺序是合理的,但其使用即不是 DFS也不是BFS。其调用算法就是 c3 算法。

C3 linearization 算法原理

首先我们定义几个符号的意义:(因为后面会用到公式表达)

符号 意义
L 针对一个类进行解析用L进行表示,例如L(A)表示对类A进行解析
merge 合并操作的一个函数(后面具体介绍)
C 表示一个类名
B 表示是C的一个子类,如果多个子类用B1,B2....表示
+ 元素列表顺序添加
tail 去除列表第一个元素,例如 tail([1,2,3,4]) = [2,3,4]

下面是一个关键定义:

L(C) = C + merge(L(B1) + L(B2) + ...+ )

merge函数是如何合并的:

  1. 首先选中merge 函数的第一个参数(也是一个列表),按照公式里的描述就是L(B1)。
  2. 取列表中第一个元素记为h,如果h没有出现其他 列表的tail中, 那么将其移到 merge函数前,提取出来,并且将这个元素在所有列表中移除,并重复 2。
  3. 如果出现在其他列表中的 tail 中,寻找下一个列表。
  4. merge 函数所有元素都被移除类创建成功,如果寻找不到下一个列表则创建失败。

看到这里可能有点懵,下面具体举一个例子:

graph TB
X(X) -->A(A)
Y(Y) -->A(A)
X(X) -->B(B)
Y(Y) -->B(B)
A(A) -->F(F)
B(B) -->F(F)
class X():
pass class Y():
pass class A(X, Y):
pass class B(X, Y):
pass class F(A, B):
pass
print(F.__mro__)

我们来解析 F的mro顺序,则首先记为 L(F),根据

L(C) = C + merge(L(B1) + L(B2) + ...+ )

公式得到:

L(F) = F + merge(L(A)+L(B))

接下来计算L(A),与L(B):

L(A) = A + merge(L(X),L(Y)) = A + merge([X],[Y]) = [A,X,Y]
L(B) = B + merge(L(X),L(Y)) = B + merge([X],[Y]) = [B,X,Y]

带入 L(F) = L(F) + merge(L(A)+L(B)) 得到:

L(F) = F + merge([A,X,Y],[B,X,Y])

下面是关键merge逻辑理解了,首先根据 merge 的说明 1,选中得到 [A,X,Y], 根据merge的说明2,选中第一个元素 A, 判断A 是否在 tail(B,X,Y) 中,即 A 是否在 [X,Y] 中,不在,将其提出来,得到:

L(F) = F + merge([A,X,Y],[B,X,Y]) = [F,A] + merge([X,Y],[B,X,Y])

接着重复 merge的2,判断 X 是否在 tail(B,X,Y)=[X,Y] 中,结果是存在,那么寻找[X,Y]的下一个列表,即[B,X,Y],判断B 是否存在 tail([X,Y])=[Y] 中,不存在,提出B,得到:

L(F) = F + merge([A,X,Y],[B,X,Y]) = [F,A] + merge([X,Y],[B,X,Y]) = [F,A,B] + merge([X,Y],[X,Y])

剩下逻辑一样,依次提出 X和Y:

L(F) = F + merge([A,X,Y],[B,X,Y]) = [F,A] + merge([X,Y],[B,X,Y]) = [F,A,B] + merge([X,Y],[X,Y]) = [F,A,B,X,Y]

可以将我上述python代码运行一下结果和我们手算的是一样的:

(<class '__main__.F'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.X'>, <class '__main__.Y'>, <class 'object'>)

复杂的解析(练手逻辑)

graph TB
O(O) --> C(C)
O(O) --> A(A)
O(O) --> B(B)
O(O) --> D(D)
O(O) --> E(E)
C(C) --> K1(K1)
A(A) --> K1(K1)
B(B) --> K1(K1)
A(A) --> K3(K3)
D(D) --> K3(K3)
B(B) --> K2(K2)
D(D) --> K2(K2)
E(E) --> K2(K2)
K1(K1) --> Z(Z)
K3(K3) --> Z(Z)
K2(K2) --> Z(Z)

L(K1) = K1 + merge(L(C), L(A), L(B))
= K1 + merge([C, O], [A, O], [B, O])
= [K1, C] + merge([O], [A, O], [B, O])
= [K1, C, A] + merge([O], [O], [B, O])
= [K1, C, A, B] + merge([O], [O], [O])
= [K1, C, A, B, O]
L(K2) = [K2, B,D,E, O]
L(K3) = [K3,A, D, O]
L(Z) = Z + merge(L(K1), L(K3), L(K2))
= Z + merge([K1, C, A, B, O],[K3, A, D, O],[K2, B, D, E, O])
= [Z, K1] + merge([C, A, B, O], [K3, A, D, O], [K2, B, D, E, O])
= [Z, K1, C] + merge([A, B, O], [K3, A, D, O], [K2, B, D, E, O])
= [Z K1, C] + merge([A, B, O], [K3, A, D, O], [K2, B, D,E, O])
= [Z, K1, C, K3] + merge([A, B, O], [A, D, O], [K2, B, D, E, O])
= [Z, K1, C, K3, A] + merge([B, O], [D, O], [K2, B, D, E, O])
= [Z,K1, C, K3, A, K2] + merge([B, O], [D, O], [B, D, E, O])
= [Z,K1, C, K3, A, K2, B] + merge([O], [D, O], [D, E, O])
= [Z, K1,C, K3, A, K2, B, D] + merge([O], [O], [E,O])
= [Z, K1,C, K3, A, K2, B, D, E, O]
class O:
pass class C(O):
pass class A(O):
pass class B(O):
pass class D(O):
pass class E(O):
pass class K1(C,A,B):
pass class K3(A,D):
pass class K2(B,D,E):
pass class Z(K1,K3,K2):
pass print(Z.__mro__)

其实我看到很多文章有这种写法:

L(K1) = K1 + merge(L(C), L(A), L(B),(C,A,B))

这个(C,A,B)写不写都可以,最后都是要删除的,很多国外网站文章习惯这么写,应该是便于理解。

手写一个C3 linearization 算法

理解了merge的原理,我想我可以简单实现一下这个算法,可能你已经想象到了针对 L 的函数需要用到递归实现,merge参数传递一个二维数组就可以。

class O:
pass class C(O):
pass class A(O):
pass class B(O):
pass class D(O):
pass class E(O):
pass class K1(C,A,B):
pass class K3(A,D):
pass class K2(B,D,E):
pass class Z(K1,K3,K2):
pass import copy
# merge_list 为一个二维的数组
def merge(merge_list):
index = 0
res = []
while index < len(merge_list): if "".join(["".join(i) for i in merge_list]) == "":
break
if merge_list[index] == []:
index += 1
first = merge_list[index][0]
t = copy.deepcopy(merge_list)
t.pop(index)
temp_all = "".join(["".join(i[1:]) for i in t])
if first not in temp_all:
for temp_list in merge_list:
if first in temp_list:
temp_list.remove(first)
res.append(first)
else:
index += 1
return res def L(arg_class):
if arg_class.__bases__[0].__name__ == 'object':
return [arg_class.__name__]
res = [arg_class.__name__]
res += merge([L(clss) for clss in arg_class.__bases__])
return res print(Z.__mro__)
print(L(Z))

我也没好好优化这个算法,反正能跑通,另外无法测试 错误继承,因为错误继承在类的实现的时候就会报错,为了方便测试我自己算法是否正确(看看__mro__属性就可以了),类的继承使用和python内置继承,没有自己写继承逻辑。

c3 linearization详解的更多相关文章

  1. c3算法详解

    c3 算法求某一类在多继承中的继承顺序:类的mro == [类] + [父类的继承顺序] + [父类2的继承顺序]如果从左到右的第一个类在后面的顺序中出现,那么就提取出来到mro顺序中[ABCD] + ...

  2. org.apache.log4j.Logger详解

    org.apache.log4j.Logger 详解 1. 概述 1.1. 背景 在应用程序中添加日志记录总的来说基于三个目的 :监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工 ...

  3. C#中string.format用法详解

    C#中string.format用法详解 本文实例总结了C#中string.format用法.分享给大家供大家参考.具体分析如下: String.Format 方法的几种定义: String.Form ...

  4. Log4j配置详解(转)

    一.Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局).这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出.综合使 ...

  5. [Java入门笔记] 面向对象编程基础(二):方法详解

    什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...

  6. Log4J日志配置详解

    一.Log4j简介 Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局).这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出.综合使 ...

  7. Linux C 字符串输入函数 gets()、fgets()、scanf() 详解

    一.gets() 函数详解 gets()函数用来从 标准输入设备(键盘)读取字符串直到 回车结束,但回车符('\n')不属于这个字符串. 调用格式为: gets(str); 其中str为字符串变量(字 ...

  8. JMeter学习-023-JMeter 命令行(非GUI)模式详解(一)-执行、输出结果及日志、简单分布执行脚本

    前文 讲述了JMeter分布式运行脚本,以更好的达到预设的性能测试(并发)场景.同时,在前文的第一章节中也提到了 JMeter 命令行(非GUI)模式,那么此文就继续前文,针对 JMeter 的命令行 ...

  9. Netsuite Formula > Oracle函数列表速查(PL/SQL单行函数和组函数详解).txt

    PL/SQL单行函数和组函数详解 函数是一种有零个或多个参数并且有一个返回值的程序.在SQL中Oracle内建了一系列函数,这些函数都可被称为SQL或PL/SQL语句,函数主要分为两大类: 单行函数 ...

  10. Log4J详解

    Log4J 简介  Log4j有三个主要的组件:Loggers(记录器),Appenders (输出源)和Layouts(布局).这里可简单理解为日志类别,日志要输出的地方和日志以何种形式输出.综合使 ...

随机推荐

  1. MySQL InnoDB缓存

    1. 背景 对于各种用户数据.索引数据等各种数据都是需要持久化存储到磁盘,然后以"页"为单位进行读写. 相对于直接读写缓存,磁盘IO的成本相当高昂. 对于读取的页面数据,并不是使用 ...

  2. java8 新特性 -Optional的常见用法

    1. Optional 一. 简介 Opitonal是java8引入的一个新类,目的是为了解决空指针异常问题.本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为 ...

  3. Typora Markdown 安装包

    下载地址: 链接:https://pan.baidu.com/s/1wy0Ik95AjM5WjSC3nzOzqA 提取码:f26j 复制这段内容后打开百度网盘手机App,操作更方便哦 已更新至最新版0 ...

  4. std:move() 作用 和 移动语义后 右值行为,unique_ptr的"移动"操作问题

    unique_ptr 不能进行赋值操作,但是可以有返回unique_ptr的函数,由此产生的问题: 结论1:std:move() 只是将一个实参强行转换为右值引用. 我们知道对象初始化时有 构造函数, ...

  5. service的dns记录

    当您创建一个 Service 时,Kubernetes 为其创建一个对应的 DNS 条目.该 DNS 记录的格式为 ..svc.cluster.local,也就是说,如果在容器中只使用 ,其DNS将解 ...

  6. Python中dataclass库

    目录 dataclass语法 一. 简介 二. 装饰器参数 三. 数据属性 1. 参数 2. 使用示例 3. 注意事项 四. 其他 1. 常用函数 2. 继承 3. 总结 dataclass语法 一. ...

  7. PTA2022 520钻石争霸赛题解

    7-1 520表白 不用说 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int max ...

  8. 重写 hashcode()真有那么简单嘛?

    万万没想到一个 hashcode() 方法,既然会引出一堆的知识盲区,简直了. 起因: 老八股:为什么重写Equals方法要重写HashCode方法. 大声告诉我为什么,闭着眼睛先把答案背出来,啥?这 ...

  9. 10.MongoDB系列之副本集组成

    1. 同步 复制是指多台服务器保持相同的数据副本.MongoDB通过保存操作日志(oplog)实现复制功能. oplog存在于主节点local数据库中的一个固定集合,包含了主节点执行的每一次写操作. ...

  10. linux 自动备份mysql数据库

    今天一早打开服务器.13W个木马.被爆破成功2次,漏洞3个.数据库被删.这是个悲伤的经历 还好之前有备份,服务器也升级了安全机制,只是备份是上个月的备份.所以想写个脚本,试试自动备份数据库. 1. 先 ...