python中mro的C3算法是如何工作的



考虑以下代码:

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

既然H直接从G继承,而只是间接从B继承,为什么在这种情况下BG之前?

C3的工作方式在这里有很好的记录。但是我们可以通过你的具体例子来了解你为什么会得到这样的结果

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

mro函数非常容易理解。它在基本情况下返回[object],也就是在object类型上调用它的时候,或者它返回一个列表,首先是它的参数,然后是在其参数的所有基的mros上调用的merge返回的值。以下是Python中一个快速而肮脏的实现:

def mro(cls):
if cls is object:
return [object]
return [cls] + merge([mro(base) for base in cls.__bases__])

合并稍微复杂一些。它的参数是一个列表列表,每个列表都是基类的mro。如果只有一个列表,那么merge的结果真的很简单(它只是同一个列表)。这是单一的继承情况。但这种行为不需要任何特殊情况下的代码,它会从算法中出现。该算法要求扫描您被传递的列表,以找到第一个有效值,并将其输入结果mro。有效值只出现在它所在的参数列表的开头,而不会出现在列表的更深处。一旦你有了一个有效的值,你就把它放在结果的首位,然后在每个列表的其余部分递归(不包括你刚刚提取的值)。

这里还有一个Python实现:

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)的调用,它运行mro函数代码的一般情况。这导致了三个递归调用,以获得EFGmro。希望这些不会让你感到困惑,因为我真的不想深入研究它们(因为算法中导致你所问的奇怪之处的部分稍后会出现)。所以在那些递归的mro调用resolve之后,我们对结果调用merge。这是我们的调用堆栈:

mro(H)->
return [H] + merge([[E, B, C, A, object], [F, B, D, A, object], [G, C, D, A, object]])

merge代码现在运行。它检查的第一个候选者是E,这是一个有效的候选者,因为E除了出现在列表的开头之外,不会出现在任何地方。因此它移除CCD_ 32并递归。新的调用堆栈:

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(正如您所期望的)。

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, ...], [B, ...], [G, ...]]) ->
[B] + merge([[C, A, object], [D, A, object], [G, C, D, A, object]])

从这里开始,事情就很简单了,因为第三个列表中的值最终会被一个接一个地删除(前面列表中的那些值也会同时删除)。我只显示调用堆栈,而不是对每个堆栈进行评论:

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, ...], [B, ...], [G, ...]]) ->
[B] + merge([[C, A, object], [D, A, object], [G, C, D, A, object]])
merge([[C, ...], [D, ...], [G, ...]]) ->
[G] + merge([[C, A, object], [D, A, object], [C, D, A, object]])
merge([[C, ...], [D, ...], [C, ...]]) ->
[C] + merge([[A, object], [D, A, object], [D, A, object]])
merge([[A, ...], [D, ...], [D, ...]]) ->
[D] + merge([[A, object], [A, object], [A, object]])
merge([[A, object], [A, object], [A, object]]) ->
[A] + merge([[object], [object], [object]])
merge([[object], [object], [object]]) ->
[object] + merge([])
merge([]) -> 
[]   # base case

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

  • 局部优先排序:

    对于class H(E,F,G)H的MRO必须在G之前的F之前具有E

  • 单调性

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

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

相关内容

  • 没有找到相关文章

最新更新