考虑以下代码:
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
继承,为什么在这种情况下B
在G
之前?
C3的工作方式在这里有很好的记录。但是我们可以通过你的具体例子来了解你为什么会得到这样的结果
线性化算法并不难理解。它有两个递归部分。我将调用mro
的公共接口(上面的文档使用L[x]
而不是mro(x)
,但我将坚持Python语法)。另一部分是一个名为merge
的辅助函数。
mro
函数非常容易理解。它在基本情况下返回[object]
,也就是在object
类型上调用它的时候,或者它返回一个列表,首先是它的参数,然后是在其参数的所有基的mro
s上调用的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
函数代码的一般情况。这导致了三个递归调用,以获得E
、F
和G
的mro
。希望这些不会让你感到困惑,因为我真的不想深入研究它们(因为算法中导致你所问的奇怪之处的部分稍后会出现)。所以在那些递归的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实际上非常简单:使用这些约束构建一个有向图,并对其进行拓扑排序。