Back to Blog
Python

Python super() Function Explained

python super function: Understand how Python's super() works, how it uses the MRO, and when to use it in single and multiple inheritance for maintainable code.

super()MROmultiple inheritancecooperative inheritancePython OOP
A diagram showing a Python class hierarchy with super() delegating method calls along the method resolution order.

The python super function is often misunderstood because it does not directly call a parent method. Instead, super() returns a proxy object that delegates method calls according to the class's method resolution order (MRO). This behavior makes it essential for cooperative multiple inheritance, but it also means that using super() incorrectly can lead to subtle bugs.

How super() Works in Single Inheritance

In the simplest case, super() lets you call a method from a parent class without naming the parent explicitly. Consider this example:

class Base: def __init__(self, value): self.value = value class Child(Base): def __init__(self, value, extra): super().__init__(value) self.extra = extra

Here, super().__init__(value) invokes Base.__init__. The proxy returned by super() is bound to the current class and instance, so it knows which class to start from and where to look next in the MRO.

This approach avoids hard-coding the parent class name, which makes refactoring easier. If you rename Base or change the inheritance hierarchy, the super() call still works as long as the MRO remains valid.

The Method Resolution Order (MRO)

Every class in Python has an __mro__ attribute that lists the classes in the order they are searched for methods. For a single inheritance chain, the MRO is straightforward: the class itself, then its parent, then the grandparent, and so on up to object.

print(Child.__mro__) # (<class 'Child'>, <class 'Base'>, <class 'object'>)

When you call super().method(), Python starts the lookup at the class after the one where super() is used. For Child, that means Base. For Base, it would mean object.

The MRO becomes more complex with multiple inheritance. Python uses the C3 linearization algorithm to produce a consistent order that respects the order of base classes and ensures that each class appears only once.

super() in Multiple Inheritance

Multiple inheritance is where super() truly shines, but it also demands discipline. Consider a diamond hierarchy:

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

When you instantiate D, the MRO is [D, B, C, A, object]. The super() calls in B and C do not skip to A directly; they follow the MRO. So A.__init__ is called exactly once, and the order of prints is A, C, B, D.

This cooperative behavior works only if every class in the hierarchy uses super() consistently. If one class calls A.__init__ directly, it breaks the chain and can cause A to be initialized twice or in the wrong order.

Common Mistakes with super()

One frequent mistake is forgetting to call super().__init__() in a subclass. If the parent class initializes required attributes, skipping the call leaves the instance incomplete. Another mistake is using the parent class name directly, like Base.__init__(self), which bypasses the MRO and breaks cooperative inheritance.

A more subtle error is mixing super() with positional arguments in a way that does not match the signatures of all cooperating classes. Because super() can call methods from multiple classes in the MRO, each class's method must accept the same arguments or use *args and **kwargs to forward them.

class A: def __init__(self, *args, **kwargs): print("A", args, kwargs) class B(A): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) print("B")

This pattern ensures that each class receives only the arguments it needs and passes the rest along.

super() with init and new

super() is not limited to __init__. It works with any method, including __new__, __str__, and custom methods. For __new__, the call is slightly different because it is a static method that receives the class as the first argument.

class Singleton: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance

Here, super().__new__(cls) delegates to the next class in the MRO, which is object in this case. The same cooperative rules apply.

Runtime Behavior and Maintainability

Because super() resolves methods dynamically at runtime, it adds a layer of indirection. This is usually negligible, but it means that the actual method called depends on the full class hierarchy. If a class is later used as a base in a new hierarchy, the behavior of super() calls can change.

This dynamic nature is both a strength and a maintenance risk. On one hand, it allows mixins to work together without knowing each other. On the other hand, it makes the flow harder to trace when the MRO is long. Tools like inspect.getmro() or simply printing __mro__ can help you understand the order.

For maintainability, prefer using super() consistently across a hierarchy. If you are writing a class that is meant to be subclassed, document that it participates in cooperative inheritance. If you are working with a third-party class that does not use super(), be cautious about mixing it with your own super() calls.

When to Use super() vs Direct Parent Calls

In a single inheritance scenario where you control the entire hierarchy, super() is the recommended way to call parent methods. It is more readable and avoids repeating the parent class name. Direct calls like Base.method(self) are only appropriate when you intentionally want to bypass the MRO, which is rare and usually a sign of a design problem.

In multiple inheritance, super() is the only reliable way to maintain cooperative behavior. Direct calls will break the chain and lead to inconsistent initialization or method invocation order. If you are writing a mixin, always use super() so that the next class in the MRO can run its own logic.

A practical decision rule: use super() when the class is part of an inheritance hierarchy that may be extended. Use a direct parent call only when you are absolutely certain that the class will never be used in multiple inheritance and that bypassing the MRO is intentional.

Understanding the python super function is not just about syntax; it is about understanding how Python resolves method calls across a hierarchy. By using super() consistently and respecting the MRO, you can write classes that compose cleanly and remain maintainable as they evolve.

python super function: Practical Usage and Code Examples | RYUSLOG DEV