Search code examples
pythonmultiple-inheritancemethod-resolution-order

Why is the initialiser of the inherited class skipped?


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.


Solution

  • 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