Python FAQ: Top Questions
19. What is the purpose of `super()` in Python?
The `super()` function in Python is used in the context of class inheritance, specifically in multi-level or multiple inheritance scenarios. Its primary purpose is to provide a way to **call a method defined in a parent (superclass) or sibling class**, without explicitly naming that parent class. This is particularly useful in cooperative multiple inheritance, where the goal is to ensure that all necessary methods in the Method Resolution Order (MRO) are called correctly.
Core Purpose:
- Delegation to Parent/Sibling Methods: `super()` allows you to delegate method calls to the next class in the Method Resolution Order (MRO). The MRO is the order in which Python searches for methods in a class hierarchy.
- Cooperative Multiple Inheritance: In complex inheritance structures (especially with multiple inheritance), `super()` plays a crucial role in ensuring that methods, particularly `__init__`, are called for all parent classes in a predictable and non-redundant way. This is often referred to as "cooperative multiple inheritance."
Syntax:
Historically, `super()` was called with arguments: `super(CurrentClass, self).method_name()`. However, in Python 3, it's often called without arguments, making it more concise:
- `super().method_name(*args, **kwargs)` (Python 3 and later): This is the preferred modern syntax. Python automatically figures out the current class and instance.
- `super(Class, instance).method_name(*args, **kwargs)`: The explicit form, useful when you need more control, such as calling a superclass method from outside its normal scope, or in specific class method contexts.
How `super()` works with MRO:
When `super()` is called, it doesn't just call the immediate parent. It consults the class's **Method Resolution Order (MRO)** to determine which method to call next. The MRO is a linear order of classes that Python follows to resolve method or attribute lookups. You can inspect the MRO of any class using `ClassName.__mro__` or `help(ClassName)`.
In cooperative multiple inheritance, `super()` ensures that each class in the MRO gets a chance to initialize itself (e.g., via `__init__`) and that methods are called correctly across the inheritance chain, preventing redundant calls or missed initializations.
Benefits of `super()`:
- Maintainability: You don't need to hardcode parent class names, making code more robust to changes in the class hierarchy.
- Flexibility: Enables proper functioning of multiple inheritance, where `__init__` methods (and other methods) need to be called in a specific order across several parent classes.
- Readability: The intent of delegating to a "higher" class in the hierarchy is clearer.
While `super()` is most powerful in multiple inheritance, it is good practice to use `super().__init__()` even in single inheritance to make your code more adaptable if the hierarchy changes later.
# --- Example 1: Single Inheritance with super() ---
print("--- Single Inheritance with super() ---")
class Parent:
def __init__(self, name):
self.name = name
print(f"Parent __init__ called for {self.name}")
def greet(self):
return f"Hello from Parent, {self.name}!"
class Child(Parent):
def __init__(self, name, age):
# Call the Parent's __init__ method using super()
# This initializes the 'name' attribute from the Parent class.
super().__init__(name) # Python 3 syntax: automatically passes Child and self
self.age = age
print(f"Child __init__ called for {self.name}, age {self.age}")
def greet(self):
# Call Parent's greet method and extend its functionality
parent_greeting = super().greet()
return f"{parent_greeting} I am {self.age} years old."
my_child = Child("Alice", 10)
print(my_child.greet())
# --- Example 2: Multiple Inheritance with super() (Diamond Problem solved) ---
print("\n--- Multiple Inheritance with super() (MRO) ---")
class A:
def __init__(self):
print("Initializing A")
# super().__init__() # This would call object's __init__ (top of MRO if not called elsewhere)
pass # No super() here for simplicity to observe chain
class B(A):
def __init__(self):
print("Initializing B")
super().__init__() # Calls A's __init__ (next in MRO)
class C(A):
def __init__(self):
print("Initializing C")
super().__init__() # Calls A's __init__ (next in MRO)
class D(B, C): # Inherits from B first, then C
def __init__(self):
print("Initializing D")
super().__init__() # Calls B's __init__ (next in MRO of D)
print("MRO for D:", D.__mro__)
# Output will typically be:
# MRO for D: (, , , , )
# Creating an instance of D
d_obj = D() # Observe the __init__ call order due to super() and MRO
print("\n--- Another Multiple Inheritance Example (Order of calls) ---")
class Base:
def __init__(self):
print("Base.__init__")
class Mixin1(Base):
def __init__(self):
print("Mixin1.__init__")
super().__init__() # Call next in MRO
class Mixin2(Base):
def __init__(self):
print("Mixin2.__init__")
super().__init__() # Call next in MRO
class FinalClass(Mixin1, Mixin2):
def __init__(self):
print("FinalClass.__init__")
super().__init__() # Calls Mixin1.__init__
print("MRO for FinalClass:", FinalClass.__mro__)
# Output will be:
# MRO for FinalClass: (, , , , )
f_obj = FinalClass() # Observe the __init__ call order
# Expected output:
# FinalClass.__init__
# Mixin1.__init__
# Mixin2.__init__
# Base.__init__
Explanation of the Example Code:
-
**Single Inheritance:**
- The `Child` class inherits from `Parent`. In `Child.__init__`, `super().__init__(name)` is called. This explicitly invokes the `__init__` method of the `Parent` class, correctly initializing the `name` attribute before `Child`'s own `age` attribute is set.
- Similarly, in `Child.greet()`, `super().greet()` calls the `greet` method of the `Parent` class, whose result is then used and extended by the `Child` class's `greet` method. This demonstrates how `super()` allows you to extend parent functionality rather than completely overriding it.
-
**Multiple Inheritance (Diamond Problem Solved):**
- This example addresses the "diamond problem" (where a class inherits from two classes that share a common ancestor). Classes `B` and `C` both inherit from `A`. Class `D` inherits from both `B` and `C`.
- If `super().__init__()` were not used (or if `A.__init__(self)` was directly called in B and C), `A.__init__` would be called twice when creating `D` (once via `B` and once via `C`), which is often undesirable.
- By using `super().__init__()` consistently in `B`, `C`, and `D`, Python's MRO ensures that `A.__init__` is called only once, and in the correct order.
- The `D.__mro__` output shows the order: `D -> B -> C -> A -> object`. When `D.__init__` calls `super().__init__()`, it goes to `B.__init__`. When `B.__init__` calls `super().__init__()`, it goes to `C.__init__` (because `C` is next in `D`'s MRO after `B`). When `C.__init__` calls `super().__init__()`, it goes to `A.__init__`. This cooperative calling ensures all `__init__` methods in the hierarchy are invoked exactly once.
-
**Another Multiple Inheritance Example (Order of calls):**
- This further solidifies the MRO's role. `FinalClass` inherits from `Mixin1` and `Mixin2`.
- The `FinalClass.__mro__` determines the exact order of method calls.
- When `f_obj = FinalClass()` is executed, the `__init__` calls flow sequentially down the MRO: `FinalClass` calls `super()` to `Mixin1`, `Mixin1` calls `super()` to `Mixin2`, `Mixin2` calls `super()` to `Base`. This ensures a predictable initialization chain.
The examples clearly demonstrate that `super()` is indispensable for maintaining correct and cooperative method invocation across class hierarchies, especially in complex multiple inheritance scenarios, by leveraging Python's Method Resolution Order.