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:
- Direct Base Classes are resolved in order they appear.
- Recursive Base Classes are resolved to not duplicate.
- A Base Class of both
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