吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1579|回复: 2
收起左侧

[Python 转载] c3 linearization详解

[复制链接]
wshuo 发表于 2022-4-7 12:29
本帖最后由 wshuo 于 2022-4-7 12:41 编辑

MRO

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

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

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

菱形继承

菱形继承

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

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

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

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


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

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 函数所有元素都被移除类创建成功,如果寻找不到下一个列表则创建失败。

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

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'>)

复杂的解析(练手逻辑)

4.png

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内置继承,没有自己写继承逻辑。


吐槽一下,52破解不支持markdown流程图,导致我还截图上传的

免费评分

参与人数 2吾爱币 +1 热心值 +2 收起 理由
RKCN + 1 用心讨论,共获提升!
randomone + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

双眼皮的微笑 发表于 2022-4-7 20:35
作为Python初学者 感觉不简单啊,楼主我的榜样。
v.n.lee 发表于 2022-4-11 21:19
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 06:34

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表