I have this code, showing a classic diamond pattern:
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()
The output is:
B:d
C:b
A:c
B:d
makes sense, since D
derives from B
.A:c
I almost get, though I could equally see A:b
.C:b
bit doesn't make sense: C
does not derive from B
.Could someone explain?
Questions such as this unfortunately do not mention the parameters.
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