Your misunderstanding is clear from this comment:
I don't override __init__
in LockableDict
, so it should generate an __init__
making automatic calls to base classes' __init__
s, shouldn't it?
No!
First, nothing gets automatically generated; the method resolution happens at call time.
And at call time, Python won't call every base class's __init__
, it will only call the first one it finds.*
This is why super
exists. If you don't explicitly call super
in an override, no base classes or even sibling classes will get their implementations called. And dict.__init__
doesn't call its super
.
Neither does your Lockable.__init__
. So, this means that reversing the order makes sure Lockable.__init__
gets called… but dict.__init__
now doesn't get called.
So, what do you actually want to do? Well, Python's MRO algorithm is designed to allow maximum flexibility for hierarchies of cooperating classes, with any non-cooperating classes thrown in at the end. So, you could do this:
class Lockable(object):
def __init__(self):
super().__init__() # super(Lockable, self).__init__() for 2.x
self._lock = None
def is_locked(self):
return self._lock is None
class LockableDict(Lockable, dict):
pass
Also notice that this allows you to pass initializer arguments along the chain through all your cooperating classes, and then just call the unfriendly class's __init__
with the arguments you know it needs.
In a few cases, you have to put an unfriendly class first.** In that case, you have to explicitly call around them:
class LockableDict(dict, Lockable):
def __init__(self):
dict.__init__(self)
Lockable.__init__(self)
But fortunately, this doesn't come up very often.
* In standard method resolution order, which is slightly more complicated than you'd expect. The same MRO rule is applied when you call super
to find the "next" class—which may be a base class, or a sibling, or even a sibling of a subclass. You may find the Wikipedia article on C3 linearization more approachable.
** Especially for built-in classes, because they're special in a few ways that aren't worth getting into here. Then again, many built-in classes don't actually do anything in their __init__
, and instead do all the initialization inside the __new__
constructor.