Python FAQ: Top Questions
45. What is Python's `super()` function and how does it work?
Python's built-in `super()` function provides a way to call a method that exists in a parent class (or an ancestor class) from a child class, especially useful in scenarios involving **multiple inheritance** and **cooperative inheritance**.
Contrary to common misconception, `super()` does not just call the direct parent's method. Instead, it delegates method calls to the **next class in the Method Resolution Order (MRO)**. This allows for a more robust and predictable way to manage inheritance in complex class hierarchies.
Syntax of `super()`:
-
**`super()` (Python 3, no arguments):**
- This is the most common and recommended way to use `super()` in Python 3. Inside an instance method, `super()` automatically determines the current class and instance, and then finds the next class in the MRO to call the method from.
- Example: `super().method_name(*args, **kwargs)`
-
**`super(type, object_or_type)` (Explicit Arguments):**
- `type`: The class from which you want to start searching in the MRO.
- `object_or_type`: The instance object (for instance methods) or the class itself (for class methods).
- This explicit form is less common in Python 3 but useful in specific metaprogramming contexts or when you need to specify the MRO starting point.
- Example: `super(MyClass, self).method_name(*args, **kwargs)`
How `super()` Works with MRO:
When `super().method_name()` is called:
- Python identifies the class from which `super()` was called (e.g., `ChildClass`).
- It then consults the Method Resolution Order (MRO) of the instance's actual class (e.g., `instance_of_GrandChildClass.__mro__`).
- It finds the `ChildClass` in the MRO and then looks for the *next class* in the MRO after `ChildClass`.
- Finally, it calls `method_name` on that "next" class, passing the original instance (`self`) to it.
This cooperative approach ensures that all relevant `method_name` implementations in the inheritance chain (according to MRO) are called, preventing issues like methods being called multiple times or being skipped entirely in complex multiple inheritance scenarios (the "diamond problem").
Why `super()` is used:
-
**Calling Parent Constructors (`__init__`):**
- The most common use case is to ensure that parent classes' `__init__` methods are called, properly initializing inherited attributes.
- `super().__init__(*args, **kwargs)` is the standard way to do this.
-
**Cooperative Multiple Inheritance:**
- Allows each class in a complex inheritance hierarchy to contribute to a shared method (e.g., a `process_data` method) by calling `super().process_data()` to delegate to the next class in the MRO. This ensures that all parts of the logic are executed.
-
**Avoiding Hardcoding Parent Names:**
- Using `super()` makes code more maintainable and robust. If you change the inheritance hierarchy (e.g., insert a new class between a child and its direct parent), `super()` automatically adapts, whereas hardcoding `ParentClass.method(self, ...)` would require manual updates.
`super()` is a powerful and essential tool for designing clean and robust class hierarchies in Python, especially when multiple inheritance is involved. It simplifies the management of method calls across the inheritance chain.
# --- Example 1: Basic Single Inheritance with super() ---
print("--- Basic Single Inheritance with super() ---")
class Parent:
def __init__(self, name):
self.name = name
print(f"Parent __init__ called for {self.name}")
def greet(self):
print(f"Hello from Parent, my name is {self.name}")
class Child(Parent):
def __init__(self, name, age):
super().__init__(name) # Calls Parent's __init__
self.age = age
print(f"Child __init__ called for {self.name}, age {self.age}")
def greet(self):
print(f"Hello from Child, my name is {self.name} and I am {self.age}")
super().greet() # Calls Parent's greet()
child_obj = Child("Alice", 30)
child_obj.greet()
print(f"\nMRO for Child: {Child.__mro__}")
# Expected: (<class '__main__.Child'>, <class '__main__.Parent'>, <class 'object'>)
# --- Example 2: Cooperative Multiple Inheritance (Diamond Problem) ---
print("\n--- Cooperative Multiple Inheritance (Diamond Problem) ---")
class Base:
def __init__(self):
print("Base __init__")
def perform_action(self):
print("Base action")
class Mixin1(Base):
def __init__(self):
print("Mixin1 __init__")
super().__init__() # Calls the next __init__ in MRO (Base's)
def perform_action(self):
print("Mixin1 action (before next)")
super().perform_action() # Calls the next perform_action in MRO (Base's)
print("Mixin1 action (after next)")
class Mixin2(Base):
def __init__(self):
print("Mixin2 __init__")
super().__init__() # Calls the next __init__ in MRO (Base's)
def perform_action(self):
print("Mixin2 action (before next)")
super().perform_action() # Calls the next perform_action in MRO (Base's)
print("Mixin2 action (after next)")
class FinalClass(Mixin1, Mixin2): # MRO: FinalClass -> Mixin1 -> Mixin2 -> Base -> object
def __init__(self):
print("FinalClass __init__")
super().__init__() # Calls Mixin1's __init__
def perform_action(self):
print("FinalClass action (before next)")
super().perform_action() # Calls Mixin1's perform_action
print("FinalClass action (after next)")
print(f"MRO for FinalClass: {FinalClass.__mro__}")
final_obj = FinalClass()
print("\nCalling perform_action:")
final_obj.perform_action()
# Expected output for __init__:
# FinalClass __init__
# Mixin1 __init__
# Mixin2 __init__
# Base __init__
# (Each __init__ is called exactly once in MRO order)
# Expected output for perform_action:
# FinalClass action (before next)
# Mixin1 action (before next)
# Mixin2 action (before next)
# Base action
# Mixin2 action (after next)
# Mixin1 action (after next)
# FinalClass action (after next)
# (Each perform_action is called in MRO order, and 'after next' parts unwind)
# --- Example 3: Explicit super() arguments (less common in Python 3) ---
print("\n--- Explicit super() arguments ---")
class Grandparent:
def show(self):
print("Grandparent show")
class ParentA(Grandparent):
def show(self):
print("ParentA show")
super().show() # Calls Grandparent.show()
class ParentB(Grandparent):
def show(self):
print("ParentB show")
super().show() # Calls Grandparent.show()
class ChildExplicit(ParentA, ParentB):
def show(self):
print("ChildExplicit show")
# Explicitly calling ParentA's show, skipping MRO for a moment
ParentA.show(self) # This is NOT recommended for cooperative inheritance
# Using explicit super() to call a specific parent's version
# You want to call ParentB's 'show' method, but from a context where
# ChildExplicit is the current class, and ParentA is the "starting point" for MRO.
# This is very specific and usually indicates a complex design.
# super(ParentA, self).show() # This would call ParentB's show if ParentB is next in MRO after ParentA
# in ChildExplicit's MRO.
print("--- Calling super from ParentB perspective ---")
super(ParentB, self).show() # From ParentB's spot in MRO, the next is Grandparent.show()
print(f"\nMRO for ChildExplicit: {ChildExplicit.__mro__}")
child_explicit_obj = ChildExplicit()
child_explicit_obj.show()
# Note: The ParentA.show(self) call bypasses the cooperative super() chain.
# The super(ParentB, self).show() call illustrates selecting a specific starting point in the MRO.
# It's usually better to stick to super().method() without arguments.
Explanation of the Example Code:
-
**Basic Single Inheritance with `super()`:**
- `Child.__init__` uses `super().__init__(name)` to correctly invoke the `__init__` method of its immediate parent, `Parent`. This ensures that `self.name` is initialized by the parent.
- Similarly, `Child.greet()` uses `super().greet()` to call `Parent.greet()`, allowing both the child's and parent's greeting logic to execute.
- The MRO for `Child` is straightforward: `Child` -> `Parent` -> `object`. `super()` correctly finds the next class in this order.
-
**Cooperative Multiple Inheritance (Diamond Problem):**
- This is where `super()` truly shines. `FinalClass` inherits from `Mixin1` and `Mixin2`, both of which inherit from `Base`. This forms a "diamond" shape.
- Notice how each `__init__` method (in `Mixin1`, `Mixin2`, `FinalClass`) explicitly calls `super().__init__()`. This ensures that every `__init__` in the MRO is executed exactly once, from `FinalClass` down to `Base`, before returning. The order is determined by `FinalClass.__mro__`.
- The `perform_action` methods also demonstrate this. When `final_obj.perform_action()` is called, the execution flow follows the MRO: `FinalClass`'s `perform_action` calls `Mixin1`'s, which calls `Mixin2`'s, which calls `Base`'s. Then, as calls return, the "after next" parts of `Mixin2`, `Mixin1`, and `FinalClass` are executed in reverse MRO order. This is the essence of cooperative multiple inheritance.
-
**Explicit `super()` Arguments:**
- `super(ParentA, self).show()`: This means "find `ParentA` in `self`'s MRO, and then call the `show` method of the *next* class in the MRO after `ParentA`." In `ChildExplicit`'s MRO (`ChildExplicit`, `ParentA`, `ParentB`, `Grandparent`, `object`), the class after `ParentA` is `ParentB`. So this specifically calls `ParentB.show()`.
- This form is rarely needed in everyday Python 3 programming but is useful for very specific metaclass or custom descriptor implementations where you need precise control over the MRO traversal. It's generally advised to use the no-argument `super()` for cleaner cooperative inheritance.
These examples illustrate the powerful and flexible behavior of `super()` in managing method calls within Python's inheritance hierarchy, especially critical for robust multiple inheritance patterns.