While setting up a class hierarchy that involves multiple inheritance I came across problems with the order of constructor/initialiser calls. To help me analyse I set up a minimal, contrived example to reproduce the issue:
There is a pair of base classes to represent mutable and immutable objects.
BaseView
allows only viewing the value member, while Base
is-a BaseView
plus it allows to set the value member.
And then there is a specialised set of classes that inherit from BaseView
and Base
respectively and adds counting of the accesses per type.
class BaseView:
def __init__(self):
print("BaseView()")
self._data = None
self._locked = False
def get(self):
if self._data:
return self._data
else:
raise RuntimeError
class Base(BaseView):
def __init__(self):
print("Base()")
super().__init__()
def set(self, _d: int):
if not self._locked:
self._data = _d
self._locked = True
else:
raise RuntimeError
class TypeAView(BaseView):
def __int__(self):
print('TypeAView()')
super().__init__()
self._numViews = 0
def get(self):
self._numViews += 1
return super().get()
class TypeA(TypeAView, Base):
def __int__(self):
print('TypeA()')
super().__init__()
self._numWrites = 0
def set(self, _d: int):
self._numWrites += 1
self._locked = False
super().set(_d)
self._locked = True
if __name__ == '__main__':
a = TypeA()
assert hasattr(a, '_numWrites')
val = 42
a.set(val)
ret = a.get()
assert val == ret
To allow to follow the initialisation order I've added prints to the __init__
methods.
When I construct an object of TypeA
I'd expect an initialiser call chain that respects the MRO:
TypeA.mro()
[__main__.TypeA, __main__.TypeAView, __main__.Base, __main__.BaseView, object]
What I get instead is
Base()
BaseView()
This will give me an incomplete object of TypeA
To prove that I've added an assertion with a membership check, that indeed fires.
Traceback (most recent call last):
File "[...]/minimal_example.py", line 52, in <module>
assert hasattr(a, '_numWrites')
AssertionError
Maybe my C++ reflexes are still too strong, but I simply cannot get my head around how Python creates this object and its different parts.
Why does it simply skip the concrete classes' initialiser and directly jumps to the base? Any pointers are appreciated.
The issue in your code is a typo error. You have __int__
instead of __init__
in the TypeAView
and TypeA
classes. Because of this , the constructors are not called instead, the base class constructors are being called.
Try this
class BaseView:
def __init__(self):
print("BaseView()")
self._data = None
self._locked = False
def get(self):
if self._data:
return self._data
else:
raise RuntimeError
class Base(BaseView):
def __init__(self):
print("Base()")
super().__init__()
def set(self, _d: int):
if not self._locked:
self._data = _d
self._locked = True
else:
raise RuntimeError
class TypeAView(BaseView):
def __init__(self): # removed typo
print('TypeAView()')
super().__init__()
self._numViews = 0
def get(self):
self._numViews += 1
return super().get()
class TypeA(TypeAView, Base):
def __init__(self): # removed typo
print('TypeA()')
super().__init__()
self._numWrites = 0
def set(self, _d: int):
self._numWrites += 1
self._locked = False
super().set(_d)
self._locked = True
if __name__ == '__main__':
a = TypeA()
assert hasattr(a, '_numWrites')
val = 42
a.set(val)
ret = a.get()
assert val == ret