Python multiple inheritance gotcha

TLDR; Always call super().__init__() in the __init__() method of the subclass.

I've started learning Python recently and came across a puzzling issue. I believe it's a very likely trap for newcomers so here is a quick write up.

Say we have 2 standard classes - nothing special about them - they both define a name property as well as a prop_a and prop_b property respectively:

class A:
    def __init__(self):
        self.name = "class A"
        self.prop_a = "prop_a"

class B:
    def __init__(self):
        self.name = "class B"
        self.prop_b = "prop_b"

And we'll create another class C that inherits from both and we'll instantiate an object with it:

class C(A, B):
    def __init__(self):
        super().__init__()
        self.prop_c = "prop_c"

c = C()

As you can expect, the name property is "taken" from the first parent class, A so c.name will return class A. And it should also inherit both prop_a and prop_b, right?! Well, not quite. Let's see what happens when we try to access them:

print(c.name) # prints "class A"
print(c.prop_a) # prints "prop_a"
print(c.prop_b) # returns an AttributeError
Traceback (most recent call last):
  File "/Users/paul/Library/Mobile Documents/com~apple~CloudDocs/1_Projects/__python/library/python-object-oriented-programming-4413110-main/multi-inheritance-gotcha.py", line 19, in 
    print(c.prop_b) # prop_b
          ^^^^^^^^
AttributeError: 'C' object has no attribute 'prop_b'. Did you mean: 'prop_a'?

HA! Same thing happens with prop_a if we change the order of the multiple inheritance to class C(B, A). So what's going on here? Why does it work for prop_a but not for prop_b? Looks like the second parent class is not being inherited at all.

The Dirty Fix

We can fix it by calling the __init__() method of the second parent class in the C class:

class C(A, B):
    def __init__(self):
        super().__init__()
        B.__init__(self)
        self.prop_c = "prop_c"

In fact we can do the same for both parent classes (no need for super().__init__()):

class C(A, B):
    def __init__(self):
        A.__init__(self)
        B.__init__(self)
        self.prop_c = "prop_c"

The Real Fix

Turns out that the behavior is caused by not calling super().__init__() in the __init__() method of the parent classes. So if we add it to both A and B classes, the problem is solved:

class A:
    def __init__(self):
        super().__init__()
        self.name = "class A"
        self.prop_a = "prop_a"

class B:
    def __init__(self):
        super().__init__()
        self.name = "class B"
        self.prop_b = "prop_b"

I still don't understand how it works internally, but it must be something to do with the way Python handles multiple inheritance internally.

The Lesson

All classes in Python ultimately inherit from a base class called object. So when we create a class like A, it's actually inheriting from object.

But nobody tells you that you should call super().__init__() in the __init__() method of all of your classes. Lesson learnt! :D

Happy coding!