How does Python’s super() work with multiple inheritance?
For example, given the following code:
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).
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
- Method Resolution Order (MRO) Explained
- How super() Determines Which Method to Call
- Practical Examples and Best Practices
- Advanced Scenarios and Edge Cases
- Controlling Method Calls Explicitly
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.
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:
- Local precedence: A parent class appears before its descendants in the MRO
- 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:
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
Thirditself - 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:
-
Identify the current class and instance:
super(Third, self)tells Python we’re starting the search fromThirdwith instanceself -
Find the next class in MRO: Python looks at the MRO and finds the class that comes after
Thirdin the sequence -
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 atThirdand looks for the next class- Next class is
First, soFirst.__init__()is called
This behavior is consistent and predictable, making it safe to use super() in complex inheritance hierarchies.
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:
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:
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:
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:
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:
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:
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:
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
-
Mastering Python Inheritance: Using Parent Methods In Child Classes | ShunChild
-
Python for AI: Week 8-Classes in Python: 3. Inheritance and Polymorphism | Medium
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__()callsFirst.__init__()because the MRO is[Third, First, Second, object], makingFirstthe next class in the sequence afterThird. -
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.