Why is the initialiser of the inherited class skipped?

37 views Asked by At

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.

1

There are 1 answers

2
Sharim09 On BEST ANSWER

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