Looking for a specific post? Checkout the Blog Index.

By Kimserey Lam with

Multi Inheritance In Object Oriented Programming

Jun 19th, 2020 - written by Kimserey with .

Multi inheritance (MI) in Python is commonly used for mixins, where functionalities are defined in mixin classes and can be used as base classes. When those classes override the same functions, the program experiences what is known as the Diamond Problem. In order to call the proper functions, the compiler employs a Method Resolution Order (MRO) mechanism. In today’s post, we’ll look at the Diamond Problem, the MRO mechanism used by Python and we’ll also quickly look at why C# doesn’t have multi inheritance.

Diamond Problem

The Diamond Problem occurs when we have B and C inheriting from A, and D inheriting from B and C.

1
2
3
4
5
  A
 / \
B   C
 \ /
  D

The inheritance forms a diamond shape which introduces multiple questions:

  • When a function from A gets overridden by both B and C, which function is called when called from D? Should it be B or should it be C?
  • When a function from A gets overridden by C, do we use the overriden function or do we use the default A function when called from D?
  • What happen if we have E inherit from C and B (reverse order from D inheritance), and F inherit from D and E?

And the diamond being a simple case, in a regular program, the inheritance tree would be more complex.

Module Resolution Mechanism

Python supports multi inheritance. The MRO used in Python is called C3 Linearization. The linearization result is the sum of the class plus a unique merge of the linearizations of each parents and the list of parents itself.

For example if we have:

1
2
3
4
5
class A
class B(A)
class C(A)
class D(B, C)
class E(D, A)

The linearization, hence the resolution order would be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
L(A) = [A]

L(B) = [B] + merge(L(A), [A])
L(B) = [B, A]

L(C) = [C] + merge(L(A), [A])
L(C) = [C, A]

L(D) = [D] + merge(L(B), L(C), [B, C])
L(D) = [D] + merge([B, A], [C, A], [B, C])
L(D) = [D, B] + merge([A], [C, A], [C])
L(D) = [D, B, C] + merge([A])
L(D) = [D, B, C, A]

L(E) = [E] + merge(L(D), L(A), [D, A])
L(E) = [E] + merge([D, B, C, A], [A], [D, A])
L(E) = [E, D, B, C, A]

For A, the only class to check would be itself. For B and C, it would check itself then check A. For D, it would check itself and then check the result of the linearization which would be, in order, B, C and A. And same for E.

Following the method resolution allows us to understand the order super() calls would occur in a Python.

For example if we had the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A:
    def sayHello(self):
        print("Hello from A")

class B(A):
    def sayHello(self):
        print("Hello from B")
        super().sayHello()

class C(A):
    def sayHello(self):
        print("Hello from C")
        super().sayHello()

class D(B, C):
    def sayHello(self):
        print("Hello from D")
        super().sayHello()

D().sayHello()

We can accuratly predic that the output would be:

1
2
3
4
Hello from D
Hello from B
Hello from C
Hello from A

If a consistent ordering of all inheritance cannot be found, the compiler will fail. For example, if we were to have the following F class:

1
class F(D, E)

The linearization would look as such:

1
2
L(F) = [F] + merge(L(D), L(E), [D, E])
L(F) = [F] + merge([D, B, C, A], [E, D, B, C, A], [D, E]) // fails as there is no obvious choice

In this case, the order is ambiguous as the two candidates would be D and E but non of them can be chosen as the overall linearization has conflicting order L(E) prevents D from being chosen while [D, E] prevents E from being chosen.

In Python, this will result in the following error:

1
2
3
class F(D, E): pass

TypeError: Cannot create a consistent method resolution order (MRO) for bases D, E

Hence Python allows us to take advantage of multi inheritance while preventing us from creating circular references in the inheritance graph.

Other Languages

While Python has allowed multi inheritance, languages like C# have completely omitted it. The reasons why are described in post from 2004.

The main reason why it was left aside was that the benefits of multi inheritance were deemed as small compared to the complexity it came with to understand how conflicts get resolved and how overridden functions get used. The second was that the implementation itself would be complex for a strongly typed language like C#. Taking into account features like generics would be complex. Fast forward to today 2020, C# still does not have multi inheritance and nothing seems to point that it will ever support it.

And that concludes today’s post!

Conclusion

In today’s post, we looked into multi inheritance, how it was handled in Python and what was the perspective of C# in regards to it. We started by looking at a common problem known as the Diamond Problem, we then looked at how Python is handling multi inheritance using C3 Linearization and lastly we looked at the point of view of C# and the reason behind not implementing multi inheritance. I hope you liked this post and I see you on the next one!

External Sources

Designed, built and maintained by Kimserey Lam.