python中的MRO的C3算法如何工作?

时间:2019-01-15 10:35:00

标签: python method-resolution-order

考虑以下代码:

class A: pass
class B(A): pass        
class C(A): pass
class D(A): pass
class E(B,C): pass
class F(B,D): pass
class G(C,D): pass
class H(E,F,G): pass

我得到class H的以下顺序:

H.mro() -> H, E, F, B, G, C, D, A, object

在这种情况下,为什么BG之前,因为H直接继承自G,而仅间接继承自B

2 个答案:

答案 0 :(得分:0)

C3的工作方式here有据可查。但是我们可以为您的特定示例解决它,以查看为什么会得到结果。

线性化算法并不难理解。它具有两个递归部分。我将称之为mro的公共接口(上面的文档使用L[x]而不是mro(x),但我会坚持使用Python语法)。另一部分是名为merge的辅助函数。

mro函数非常易于理解。在以[object]类型调用的情况下,它返回object,或者先返回带有参数的列表,然后返回由merge返回的值其论据的所有基础的{1}}个。这是Python中的一种快速而肮脏的实现:

mro

合并有点复杂。它的参数是一个列表列表,每个列表都是基类的def mro(cls): if cls is object: return [object] return [cls] + merge([mro(base) for base in cls.__bases__]) 。如果只有一个列表,则mro的结果非常简单(它是相同的列表)。那是唯一的继承案例。但是这种行为不需要任何特殊情况的代码,它会从算法中出现。该算法说要扫描您通过的列表,以便找到要放入结果mro的第一个有效值。有效值是仅出现在其所在参数列表开头的值,永远不会出现在列表的更深处。获得有效值后,将其放在结果的开头,然后递归每个列表的其余部分(不包括刚刚提取的值)。

这也是它的Python实现:

merge

创建def merge(mros): if not any(mros): # all lists are empty return [] # base case for candidate, *_ in mros: if all(candidate not in tail for _, *tail in mros): return [candidate] + merge([tail if head is candidate else [head, *tail] for head, *tail in mros]) else: raise TypeError("No legal mro") 的列表参数列表的列表理解有些棘手。只是删除列表开始处出现的对merge的引用,然后丢弃之后空出的所有列表(这样我们就知道何时停止递归)。

无论如何,让我们看看调用candidate时这些函数如何处理类层次结构。

第一个调用是mro(H),它运行mro(H)函数代码的一般情况。这导致三个递归调用,以获取mromroE的{​​{1}}。希望那些不会让您感到困惑的人,因为我真的不想深入探讨它们(作为算法的一部分,导致您要问的是奇怪的问题会在以后出现)。因此,在这些递归F调用解析之后,我们对结果调用G。这是我们的调用堆栈:

mro

merge代码现在运行。它检查的第一个候选者是mro(H)-> return [H] + merge([[E, B, C, A, object], [F, B, D, A, object], [G, C, D, A, object]]) ,并且它是有效的候选者,因为merge除了出现在列表的开头之外没有出现在其他任何位置。因此它将删除E并递归。新通话堆栈:

E

在下一次运行时,它首先尝试E作为候选者,但这并不好,因为它在第二个列表中排名第二。因此继续尝试mro(H)-> [H] + merge([[E, B, C, A, object], [F, B, D, A, object], [G, C, D, A, object]]) merge([[E, ...], [F, ...], [G, ...]]) -> [E] + merge([[B, C, A, object], [F, B, D, A, object], [G, C, D, A, object]]) ,这是有效的:

B

对于您的问题,下一个电话很有趣。 F代码中要测试的第一个候选者是mro(H)-> [H] + merge([[E, B, C, A, object], [F, B, D, A, object], [G, C, D, A, object]]) merge([[E, ...], [F, ...], [G, ...]]) -> [E] + merge([[B, C, A, object], [F, B, D, A, object], [G, C, D, A, object]]) merge([[B, ...], [F, ...], [G, ...]]) -> [F] + merge([[B, C, A, object], [B, D, A, object], [G, C, D, A, object]]) ,因为它是第一个列表的开头。事实证明这一次是有效的,因为它也是第二个列表的开头(而不是上一次的结尾),并且根本不在第三个列表中。因此,它是在递归之前删除的结果,而不是merge(如您所期望的那样)。

B

从这里开始,事情变得非常简单,因为第三个列表中的值最终将被一个一个地删除(先前列表中的值也同时被删除)。我只显示调用堆栈,而不是对每个调用堆栈进行评论:

G

答案 1 :(得分:0)

不幸的是,C3不在乎直接间接。它关心:

  • 本地优先级排序

    对于HE的MRO必须在{{1}之前的F之前具有G

  • 单调

    对于class H(E,F,G),必须将E的MRO和F的MRO和G的MRO保留在H的MRO中。

    < / li>

C3实际上非常简单:使用这些约束构建有向图,并对其进行拓扑排序。