考虑以下代码:
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
在这种情况下,为什么B
在G
之前,因为H
直接继承自G
,而仅间接继承自B
?
答案 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)
函数代码的一般情况。这导致三个递归调用,以获取mro
,mro
和E
的{{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不在乎直接或间接。它关心:
本地优先级排序:
对于H
,E
的MRO必须在{{1}之前的F
之前具有G
。
单调:
对于class H(E,F,G)
,必须将E
的MRO和F
的MRO和G
的MRO保留在H
的MRO中。
C3实际上非常简单:使用这些约束构建有向图,并对其进行拓扑排序。