我有这个代码,显示一个经典的菱形图案:
class A:
def __init__( self, x ):
print( "A:" + x )
class B( A ):
def __init__( self, x ):
print( "B:" + x )
super().__init__( "b" )
class C( A ):
def __init__( self, x ):
print( "C:" + x )
super().__init__( "c" )
class D( B, C ):
def __init__( self ):
super().__init__( "d" )
d = D()
输出为:
B:d
C:b
A:c
B:d
是有道理的,因为D
源于B
.- 我几乎得到
A:c
,尽管我同样可以看到A:b
. - 但是,
C:b
位没有意义:C
不是从B
派生的。
有人可以解释一下吗?
不幸的是,诸如此类的问题没有提到参数。
Python 使用 C3 线性化算法来建立方法解析顺序,这与super
委托的顺序相同。
基本上,该算法为包含该类的每个类及其继承的每个类保留列表,以及相关类继承自的所有类。 然后,它通过逐个获取未被任何未经检查的类继承的类来构造类的顺序,直到它到达根,object
。 下面,为了简洁起见,我使用O
object
:
L(O) = [O]
L(A) = [A] + merge(L(O), [O]) = [A, O]
L(B) = [B] + merge(L(A), [A]) = [B] + merge([A, O], [A]) = [B, A] + merge([O])
= [B, A, O]
L(C) = [C] + merge(L(A), [A]) = [C] + merge([A, O], [A]) = [C, A] + merge([O])
= [C, A, O]
L(D) = [D] + merge(L(B), L(C), [B, C]) = [D] + merge([B, A, O], [C, A, O], [B, C])
= [D, B] + merge([A, O], [C, A, O], [C]) = [D, B, C] + merge([A, O], [A, O])
= [D, B, C, A, O]
Python 中的类是动态组合的 - 包括继承。
C:b
输出并不意味着B
神奇地继承自C
。如果你实例化B
或C
,没有人知道另一个。
>>> B('root')
B:root
A:b
但是,D
确实知道B
和C
:
class D(B,C):
...
在这方面有很多可用的技术细节。但是,这基本上有两个部分:
- 直接基类按其显示顺序进行解析。
B
先于C
.
- 递归基类解析为不重复。
- 同时包含
B
和C
的基类必须同时遵循两者。
- 同时包含
对于类D
,这意味着基类解析为B->C->A
!C
潜入B
和A
之间——但仅限于D
班,而不是B
班。
请注意,实际上还涉及另一个类:默认情况下,所有类都派生自object
。
>>> D.__mro__
(__main__.D, __main__.B, __main__.C, __main__.A, object)
您已经编写了A
知道没有基础来获取其参数。但是,B
和C
都不能假设这一点。它们都希望从A
对象派生。不过,子类化确实意味着B
和C
A
对象也是有效的!
B
和C
在B
和C
之前都是有效的,因为两者是A
的子类。B->C->A->object
并没有打破B
期望其超类为A
型
。对于所有其他组合,最终C
在无(无效(或object
在某物之前(无效(。这排除了深度优先的分辨率B->A->object->C
并复制了B->A->object->C->A->object
。
此方法解析顺序对于启用mixins非常实用:依赖于其他类来定义如何解析方法的类。
有一个很好的例子说明字典访问记录器如何同时接受dict
和OrderedDict
。
# basic Logger working on ``dict``
class LoggingDict(dict):
def __setitem__(self, key, value):
logging.info('Settingto %r' % (key, value))
super().__setitem__(key, value)
# mixin of different ``dict`` subclass
class LoggingOD(LoggingDict, collections.OrderedDict):
pass
您始终可以检查任何类应具有的方法解析顺序:
>>> D.mro()
[__main__.D, __main__.B, __main__.C, __main__.A, object]
如您所见,如果每个人都在做正确的事情(即调用超级(,MRO 将是第一父母、第二父母、第一父母的父母等等......
您可以先考虑深度,然后从左到右找到顺序,尽管自python 2.3以来算法发生了变化,但结果通常是相同的。
在这种情况下,B 和 C 具有相同的父 A,并且 A 不调用 super