The super() function is one of Python's most powerful yet misunderstood features. Available since Python 2.2, it lets you call methods from parent classes elegantly, especially in multiple inheritance scenarios. If you've ever written super().__init__() inside a class, you have used super() — but you have probably not explored its full potential.
In this complete guide, you will understand what super() does, how it solves the Method Resolution Order (MRO) problem, how to use it in single and multiple inheritance, and which pitfalls to avoid. Let's dive deep with practical examples you can test in your own environment.
The Problem super() Solves
Before super() existed, Python developers had to call parent class methods explicitly by name. This worked well for single inheritance but created serious issues with multiple inheritance, especially the diamond problem — where a class inherits from two classes sharing a common ancestor.
# The problem: explicit class name calls
class Parent:
def method(self):
print("Parent.method called")
class Child(Parent):
def method(self):
Parent.method(self) # Explicit call
print("Child.method called")
This approach has three major drawbacks:
- Tight coupling: you must know the exact parent class name
- Hard maintenance: changing the hierarchy requires updating every explicit call
- Broken multiple inheritance: explicit calls bypass the MRO, skipping important classes in the chain
The super() function solves all these problems at once by providing a dynamic, hierarchy-aware way to delegate calls to ancestor methods.
Basic Syntax and How It Works
The most common way to use super() is without arguments inside an instance method:
class Parent:
def method(self):
print("Parent.method executed")
class Child(Parent):
def method(self):
super().method() # Delegates to Parent.method
print("Child.method executed")
c = Child()
c.method()
Output:
Parent.method executed
Child.method executed
When you call super() without arguments, Python automatically infers the current class and instance. This only works inside instance methods that receive self as the first parameter. The equivalent explicit form would be super(Child, self).method().
The object returned by super() is not the parent class itself. Instead, it is a proxy that knows how to traverse the Method Resolution Order (MRO) to find the next method in the inheritance chain.
super() in __init__
The most common real-world use of super() is inside the __init__ method to initialize the parent class portion of an object. This pattern is essential for building clean, reusable class hierarchies. To better understand how __init__ fits into Python's object model, check out our guide on Python magic methods. The official __init__ documentation also explains how this method is invoked during object creation.
class Animal:
def __init__(self, name):
self.name = name
print(f"Animal.__init__: {self.name}")
class Dog(Animal):
def init(self, name, breed):
super().init(name) # Calls Animal.init
self.breed = breed
print(f"Dog.init: {self.name}, {self.breed}")
rex = Dog("Rex", "German Shepherd")
Output:
Animal.init: Rex
Dog.init: Rex, German Shepherd
Notice that we only pass the arguments the parent class needs. The super() function takes care of calling the correct __init__ up the hierarchy. This keeps each class responsible for its own initialization, following the single responsibility principle.
Understanding the MRO (Method Resolution Order)
The MRO is the algorithm Python uses to determine which base class method to call when you invoke a method on an instance. In Python 3, the MRO is based on the C3 Linearization Algorithm, which guarantees three important properties:
- Local consistency: a class always comes before its subclasses
- Order preservation: the order of base classes in the class definition is respected
- Monotonicity: the order does not change when new classes are added
You can inspect the MRO of any class using the __mro__ attribute or the mro() method:
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
class C(A):
def method(self):
print("C")
class D(B, C):
pass
print(D.mro)
(<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
d = D()
d.method() # "B" — follows the MRO
In the example above, the MRO for D is: D → B → C → A → object. This means that when super() is called inside B, Python will go to C, not directly to A. This is what makes cooperative multiple inheritance possible. Check the official documentation on inheritance for more context.
Cooperative Multiple Inheritance with super()
The true power of super() shines in multiple inheritance scenarios. When all classes in the hierarchy consistently use super(), Python can orchestrate each method call in the correct order, even in complex hierarchies. This pattern is known as cooperative multiple inheritance or chain of delegation.
class Worker:
def __init__(self, name):
self.name = name
print(f"Worker.__init__({self.name})")
class Manager(Worker):
def init(self, name, department):
print(f"Manager.init started for {name}")
super().init(name)
self.department = department
print(f"Manager.init done: {self.department}")
class Programmer(Worker):
def init(self, name, language):
print(f"Programmer.init started for {name}")
super().init(name)
self.language = language
print(f"Programmer.init done: {self.language}")
class TechLead(Manager, Programmer):
def init(self, name, department, language):
print(f"TechLead.init started for {name}")
super().init(name, department, language)
print("TechLead.init done")
Testing
tl = TechLead("Anna", "Engineering", "Python")
The MRO ensures Worker.init is called only ONCE
print(TechLead.mro)
Without cooperative super(), Worker.__init__ would be called twice, causing duplicated initialization and potential side effects. The MRO ensures each __init__ in the chain executes exactly once. This pattern is explained in depth by Raymond Hettinger in "Python's super() considered super!", a must-read for anyone working with complex hierarchies.
Consistent Signatures (The *args, **kwargs Pattern)
One of the biggest challenges in cooperative multiple inheritance is handling different parameter sets. The classic solution is to use *args and **kwargs to accept any combination of arguments and forward them through super():
class Vehicle:
def __init__(self, **kwargs):
self.vehicle_type = kwargs.get('vehicle_type', 'generic')
print(f"Vehicle.__init__: type={self.vehicle_type}")
class Motorized:
def init(self, **kwargs):
self.fuel = kwargs.get('fuel', 'gasoline')
print(f"Motorized.init: fuel={self.fuel}")
class Car(Motorized, Vehicle):
def init(self, kwargs):
self.brand = kwargs.get('brand', 'unknown')
self.model = kwargs.get('model', 'unknown')
print(f"Car.init: brand={self.brand}, model={self.model}")
super().init(kwargs)
my_car = Car(brand="Toyota", model="Corolla", vehicle_type="sedan", fuel="hybrid")
This cooperative super() call pattern is widely used in Python frameworks like Django, SQLAlchemy, and Flask. Each class extracts the arguments it cares about and passes the rest further up the chain.
The Explicit Form: super(Type, Object)
While the no-argument form is most common, super() also accepts explicit arguments: super(Type, object). This is useful in advanced scenarios such as class methods (@classmethod):
class Worker:
@classmethod
def create(cls, name):
print(f"Worker.create called for {name}")
return cls(name=name)
class Manager(Worker):
@classmethod
def create(cls, name, department="General"):
print(f"Manager.create called for {name}")
Inside @classmethod, super() needs cls
instance = super(Manager, cls).create(name=name)
instance.department = department
return instance
m = Manager.create("Carlos", "IT")
print(f"{m.name}, {m.department}")
Inside class methods, super() without arguments also works in Python 3, but the explicit form makes the intent clearer. For static methods (@staticmethod), super() cannot be used since there is no class or instance reference.
Common Pitfalls and How to Avoid Them
1. Forgetting to Call super().__init__()
If you define __init__ in a child class without calling super().__init__(), the parent class will not be initialized. This is one of the most common bugs:
class Parent:
def __init__(self):
self.value = 42
class Child(Parent):
def init(self):
pass # Forgot to call super().init()
c = Child()
print(c.value) # AttributeError: 'Child' object has no attribute 'value'
2. Base Class Order in Class Definition
The order in which you list base classes determines the MRO. Getting the order wrong can lead to unexpected behavior:
class A:
def action(self): print("A")
class B:
def action(self): print("B")
class C(A, B): # A takes priority
pass
class D(B, A): # B takes priority
pass
C().action() # "A"
D().action() # "B"
3. Breaking the super() Chain
If any class in the hierarchy does not call super(), the chain is broken and no class after it will execute:
class A:
def method(self): print("A")
class B(A):
def method(self):
print("B")
Did not call super().method() — chain broken
class C(B):
def method(self):
super().method()
print("C")
c = C()
c.method()
Only "B" and "C" are printed — "A" is never reached
Real-World Use Cases
Mixins and Composition
Mixins are small classes that add specific functionality. Combined with super(), they let you compose complex behaviors in a modular way:
class JSONMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class LoggerMixin:
def log(self, message):
print(f"[LOG] {message}")
class User(LoggerMixin, JSONMixin):
def init(self, name, email):
self.name = name
self.email = email
self.log(f"User {name} created")
u = User("Anna", "[email protected]")
print(u.to_json())
Frameworks and ORMs
Frameworks like Django and SQLAlchemy use super() extensively in their class hierarchies. When you define a Django model and call super().save(), you are participating in a cooperative chain that manages validation, signals, and persistence. The official super() documentation has additional examples that complement this guide.
super() vs Explicit Calls: A Comparison
It is worth seeing the practical difference between using super() and calling the parent method explicitly:
class A:
def action(self): print("A", end=" ")
class B(A):
def action(self):
A.action(self) # Explicit — ignores MRO
print("B", end=" ")
class C(A):
def action(self):
A.action(self) # Explicit — ignores MRO
print("C", end=" ")
class D(B, C):
def action(self):
B.action(self) # Only calls B and A, skips C
print("D", end=" ")
D().action() # Output: "A B D" — C was skipped!
With super():
class A2:
def action(self): print("A2", end=" ")
class B2(A2):
def action(self):
super().action()
print("B2", end=" ")
class C2(A2):
def action(self):
super().action()
print("C2", end=" ")
class D2(B2, C2):
def action(self):
super().action()
print("D2", end=" ")
D2().action() # Output: "A2 C2 B2 D2" — correct!
The difference is clear: with explicit calls, you lose the fine-grained control that the MRO provides. With super(), the hierarchy is fully respected.
Conclusion
The super() function is an indispensable tool for anyone working with object-oriented programming in Python. It is not just syntactic sugar for calling parent methods — it is a sophisticated mechanism that enables cooperative multiple inheritance while keeping your code clean, modular, and maintainable.
Key takeaways:
super()follows the MRO, not just the direct parent class- Every class in the hierarchy must use
super()for cooperation to work - The
*args, **kwargspattern is essential for flexible signatures - Inspect the MRO with
Class.__mro__when in doubt about execution order
After mastering super(), your next step is to deepen your knowledge of object-oriented programming in Python, exploring topics like metaclasses, descriptors, and advanced composition.
To go even further, check out PEP 3135 — New Super, which documents the simplified no-argument super() syntax introduced in Python 3. The Real Python tutorial on super() offers additional hands-on examples, and the Stack Overflow discussion on super() addresses the most common community questions.
Happy coding! 🐍