0

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.

twil
  • 83
  • 7

1 Answers1

2

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
codester_09
  • 5,622
  • 2
  • 5
  • 27
  • 1
    Unbelievable that I missed this! That's Pycharm trying to be helpful, I picked the wrong completion it offered when starting to type `def __in` – twil Jun 28 '23 at 15:52
  • 1
    Now I am correctly getting ```TypeA() TypeAView() Base() BaseView()``` – twil Jun 28 '23 at 15:54