I have this code, showing a classic diamond pattern:
class A:
def __init__( self, x ):
print( \"A:\" + x )
class B( A ):
def __init__( self
Classes in Python are dynamically composed - that includes inheritance.
The C:b
output does not imply that B
magically inherits from C
. If you instantiate either B
or C
, none knows about the other.
>>> B('root')
B:root
A:b
However, D
does know about both B
and C
:
class D(B,C):
...
There is a lot of technicalities available on this. However, there are basically two parts in how this works:
B
comes before C
.B
and C
must follow both.For the class D
, that means the base classes resolve as B->C->A
! C
has sneaked in between B
and A
- but only for class D
, not for class B
.
Note that there is actually another class involved: all classes derive from object
by default.
>>> D.__mro__
(__main__.D, __main__.B, __main__.C, __main__.A, object)
You have already written A
knowing that there is no base to take its parameters. However, neither B
nor C
can assume this. They both expect to derive from an A
object. Subclassing does imply that both B
and C
are valid A
-objects as well, though!
It is valid for both B
and C
to precede B
and C
, since the two are subclasses of A
. B->C->A->object
does not break that B
expects its super class to be of type A
.
With all other combinations, one ends up with C
preceding nothing (invalid) or object
preceding something (invalid). That rules out depth-first resolution B->A->object->C
and duplicates B->A->object->C->A->object
.
This method resolution order is practical to enable mixins: classes that rely on other classes to define how methods are resolved.
There is a nice example of how a logger for dictionary access can accept both dict
and 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
Python uses the C3 linearization algorithm to establish the method resolution order, which is the same order that super
delegates in.
Basically, the algorithm keeps lists for every class containing that class and every class it inherits from, for all classes that the class in question inherits from. It then constructs an ordering of classes by taking classes that aren't inherited by any unexamined classes one by one, until it reaches the root, object
. Below, I use O
for object
for brevity:
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]
You can always check the method resolution order that any class should have:
>>> D.mro()
[__main__.D, __main__.B, __main__.C, __main__.A, object]
As you can see, if everybody is doing the right thing (i.e. calling super), the MRO will be 1st parent, 2nd parent, 1st parent's parent and so on...
You can just think of depth first and then left to right to find the order although ever since python 2.3 the algorithm changed but the outcome is usually the same.
In this case B and C have the same parent A and A doesn't call super