NeuroAgent

How Python's super() Works with Multiple Inheritance

Learn how Python's super() function determines which parent method to call in multiple inheritance scenarios using Method Resolution Order (MRO). Understand the C3 linearization algorithm and how to control method calls.

Question

How does Python’s super() work with multiple inheritance?

For example, given the following code:

python
class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Which parent method of Third does super().__init__ refer to? Can I choose which parent method runs?

I know it has something to do with method resolution order (MRO).

NeuroAgent

Python’s super() function with multiple inheritance follows the Method Resolution Order (MRO) determined by the C3 linearization algorithm. In your example, super(Third, self).__init__() will call First.__init__() because the MRO lists First before Second in the inheritance chain. While you cannot directly change the MRO order, you can choose which parent method runs by calling it explicitly instead of using super().

Contents


Understanding super() and Multiple Inheritance

The super() function in Python is designed to help you call methods from parent classes, especially useful in multiple inheritance scenarios. When you use super(), Python doesn’t simply call the immediate parent class - it follows a specific algorithm called the Method Resolution Order (MRO).

In multiple inheritance, a class can inherit from multiple parent classes, which creates complexity in determining which method to call when that method exists in multiple parent classes. The MRO provides a consistent way to resolve this ambiguity.

python
class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()  # This calls First.__init__(), not Second.__init__()
        print "that's it"

In this example, when you create a Third instance, the output will be:

first
that's it

Notice that second is never printed, because super(Third, self).__init__() calls First.__init__() instead of Second.__init__().


Method Resolution Order (MRO) Explained

The Method Resolution Order (MRO) is the sequence in which Python searches for methods and attributes in a class hierarchy. Python uses the C3 linearization algorithm to determine this order, which ensures a consistent and predictable method lookup.

According to the Wikipedia article on multiple inheritance, Python creates a list of classes using the C3 linearization (or Method Resolution Order (MRO)) algorithm. The order of inheritance affects the class semantics.

The C3 linearization algorithm guarantees:

  1. Local precedence: A parent class appears before its descendants in the MRO
  2. Monotonicity: If a class appears in the MRO of two different subclasses, it appears in the same order in both MROs

You can inspect the MRO of any class using the __mro__ attribute:

python
print(Third.__mro__)
# Output: (<class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class 'object'>)

The MRO for Third is [Third, First, Second, object], which means:

  • Start with Third itself
  • Then First (first parent in inheritance list)
  • Then Second (second parent in inheritance list)
  • Finally object (ultimate base class in Python 3)

How super() Determines Which Method to Call

When you call super().__init__(), Python follows these steps:

  1. Identify the current class and instance: super(Third, self) tells Python we’re starting the search from Third with instance self

  2. Find the next class in MRO: Python looks at the MRO and finds the class that comes after Third in the sequence

  3. Call the method on that class: Python calls the __init__ method on the next class in the MRO

In your example:

  • MRO: [Third, First, Second, object]
  • super(Third, self).__init__() starts at Third and looks for the next class
  • Next class is First, so First.__init__() is called

This behavior is consistent and predictable, making it safe to use super() in complex inheritance hierarchies.

python
class A(object):
    def method(self):
        print "A"

class B(A):
    def method(self):
        super(B, self).method()
        print "B"

class C(A):
    def method(self):
        super(C, self).method()
        print "C"

class D(B, C):
    def method(self):
        super(D, self).method()
        print "D"

# Output when calling D().method():
# A
# C
# B
# D

The MRO for D is [D, B, C, A, object], so each super().method() call moves to the next class in this sequence.


Practical Examples and Best Practices

Diamond Inheritance Pattern

A common pattern in multiple inheritance is the diamond pattern, where two classes inherit from the same base class:

python
class Base(object):
    def __init__(self):
        print "Base"

class Left(Base):
    def __init__(self):
        super(Left, self).__init__()
        print "Left"

class Right(Base):
    def __init__(self):
        super(Right, self).__init__()
        print "Right"

class Diamond(Left, Right):
    def __init__(self):
        super(Diamond, self).__init__()
        print "Diamond"

# Output when creating Diamond():
# Base
# Right
# Left
# Diamond

The MRO for Diamond is [Diamond, Left, Right, Base, object], so each super().__init__() calls the next class in this sequence.

Method Chaining with super()

The proper way to use super() in multiple inheritance is to always call it and let it handle the method resolution:

python
class Animal(object):
    def __init__(self):
        print "Animal init"

class Mammal(Animal):
    def __init__(self):
        super(Mammal, self).__init__()
        print "Mammal init"

class Winged(object):
    def __init__(self):
        super(Winged, self).__init__()
        print "Winged init"

class Bat(Mammal, Winged):
    def __init__(self):
        super(Bat, self).__init__()
        print "Bat init"

# Output:
# Animal init
# Winged init
# Mammal init
# Bat init

This approach ensures that all parent class initializers are called in the correct order, following the MRO.


Advanced Scenarios and Edge Cases

Complex Multiple Inheritance

When dealing with more complex inheritance hierarchies, understanding the MRO becomes crucial:

python
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E(C, A): pass
class F(D, E): pass

print(F.__mro__)
# Output: (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

The C3 algorithm ensures that the MRO is consistent and avoids the “deadly diamond of death” problem.

super() with Different Arguments

super() can be used with different method signatures, but you need to be careful about argument passing:

python
class Parent(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
        print "Parent init"

class Child(Parent):
    def __init__(self, x, y, z):
        super(Child, self).__init__(x, y)  # Pass only the arguments Parent expects
        self.z = z
        print "Child init"

Controlling Method Calls Explicitly

While super() follows the MRO, you can choose which parent method to call explicitly:

Direct Method Calls

To call a specific parent class’s method, you can call it directly:

python
class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        # Call First.__init__() explicitly
        First.__init__(self)
        print "that's it"

# Output:
# first
# that's it

Calling Any Specific Parent

You can call any parent class’s method regardless of inheritance order:

python
class Third(First, Second):
    def __init__(self):
        # Call Second.__init__() even though First comes first in inheritance
        Second.__init__(self)
        print "that's it"

# Output:
# second
# that's it

Mixed Approaches

You can combine super() with direct calls for more complex scenarios:

python
class First(object):
    def method(self):
        print "first"

class Second(object):
    def method(self):
        print "second"

class Third(First, Second):
    def method(self):
        super(Third, self).method()  # Calls First.method()
        Second.method(self)         # Explicitly calls Second.method()
        print "that's it"
        
# Output:
# first
# second
# that's it

However, be cautious when mixing approaches, as this can lead to methods being called multiple times or in unexpected orders, potentially causing bugs.

Sources

  1. Understanding Method Resolution Order (MRO) in Python: How Python Decides Which Method to Call | Python in Plain English

  2. Multiple inheritance - Wikipedia

  3. Mastering Python Inheritance: Using Parent Methods In Child Classes | ShunChild

  4. Python for AI: Week 8-Classes in Python: 3. Inheritance and Polymorphism | Medium

  5. Inheritance (object-oriented programming) - Wikipedia

Conclusion

  • super() follows MRO: Python’s super() function uses the Method Resolution Order (MRO) determined by the C3 linearization algorithm to decide which parent method to call.

  • In your example: super(Third, self).__init__() calls First.__init__() because the MRO is [Third, First, Second, object], making First the next class in the sequence after Third.

  • Choosing parent methods: While you cannot change the MRO order, you can explicitly call any parent class’s method by referencing it directly (e.g., Second.__init__(self)).

  • Best practices: Use super() consistently in inheritance hierarchies to ensure proper method chaining and avoid duplicate calls. Only use direct method calls when you specifically need to bypass the MRO.

  • MRO inspection: Always check the __mro__ attribute of your classes to understand the method lookup order, especially when dealing with complex multiple inheritance scenarios.