Back to Blog
Python

Python super() Method Call: Syntax and Behavior

python super method call: Explains how super() resolves the next class in the MRO, covers cooperative multiple inheritance, common mistakes, and when direct parent cal...

super()method resolution ordermultiple inheritancePython OOPcooperative inheritance
Diagram showing how a super() call in a Python class resolves to the next class in the method resolution order.

When you write super().method() inside a class, Python does not look up the method on the literal parent class. Instead, it returns a proxy object that resolves the next class in the method resolution order (MRO) for the instance's actual type. The python super method call pattern is the standard way to delegate to a parent implementation while keeping the code correct under inheritance changes.

What super() Actually Returns

super() returns a proxy object, not the parent class itself. When you call a method through that proxy, Python walks the MRO of the instance's type and finds the first class after the class where the call appears that defines the requested attribute.

class Base: def describe(self): return "Base" class Child(Base): def describe(self): parent = super().describe() return f"Child -> {parent}" print(Child().describe())

The output is Child -> Base. The proxy resolves describe on Base because Base is the next class in Child's MRO after Child itself. The proxy is recreated on each access, so there is no shared state to manage.

Basic Syntax for Calling a Parent Method

The two-argument form super(Child, self) was common before Python 3. The zero-argument form super() relies on the compiler inserting the current class and instance automatically. In Python 3, super() works inside any method that receives the instance as the first parameter.

import time class Logger: def log(self, message): print(f"[log] {message}") class TimestampedLogger(Logger): def log(self, message): super().log(f"{time.time():.2f} {message}")

The zero-argument form only works inside a method defined in a class body. If you need a reference to the proxy outside a method, you must pass the class and instance explicitly, as in super(Child, self). The two-argument form still exists and is occasionally necessary in decorators or dynamically generated classes.

How the MRO Determines the Next Class

Every class has an __mro__ attribute that lists the class itself, its ancestors, and object in the order Python uses for attribute lookup. super() starts after the class in which the call appears and searches forward through that list.

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

The MRO is D, B, C, A, object. Calling D().work() produces:

A.work
C.work
B.work
D.work

Each super().work() call moves to the next class in the MRO, not to the literal parent listed in the class definition. This is why the call chain follows the MRO rather than the inheritance declaration order alone. The C3 linearization algorithm determines this order, and it guarantees that each class appears before its parents and that the order is consistent across the hierarchy.

Cooperative Multiple Inheritance

When several classes in a hierarchy each call super() for the same method, the entire chain runs in MRO order. This works only if every class in the chain cooperates by calling super() rather than hard-coding a parent class name.

class Base: def __init__(self): self.ready = True class MixinA: def __init__(self): self.a = 1 super().__init__() class MixinB: def __init__(self): self.b = 2 super().__init__() class Combined(MixinA, MixinB, Base): def __init__(self): super().__init__()

The MRO for Combined is Combined, MixinA, MixinB, Base, object. Each __init__ calls super().__init__(), so the chain runs completely. If any class used Base.__init__(self) directly, the chain would break and later mixins would never run. This cooperative pattern is what makes mixins composable in Python.

Common Mistakes When Calling super()

The most frequent error is forgetting to call super() in __init__, which leaves the parent state uninitialized. Another common mistake is mixing super() calls with direct parent-class calls in the same hierarchy. Direct calls bypass the MRO and cause inconsistent initialization order.

A subtle issue appears when a class calls super() with an explicit argument that no longer matches the actual class hierarchy. For example, super(Child, self).method() inside a grandchild class resolves relative to Child, not relative to the class where the call physically appears. This produces surprising behavior when the hierarchy changes, because the resolution starts from Child rather than from the class that contains the call.

Another edge case is calling super() in a classmethod. The zero-argument form works there too, but the second argument is the class itself, not an instance. The proxy then binds methods to the class rather than to an instance, which is the correct behavior for classmethods but easy to overlook when reading code.

When a Direct Parent Class Call Is Preferable

Calling ParentClass.method(self) directly is acceptable when the class is a leaf in a single-inheritance hierarchy and you intentionally want to bypass the MRO. This happens in legacy code or when a mixin deliberately overrides behavior and must not delegate further. The tradeoff is maintainability: renaming the parent class requires updating every direct call, and the code no longer participates in cooperative dispatch.

A direct call is also appropriate when the method you need exists only on a specific ancestor and you do not want any intermediate class to intercept the call. For example, if a mixin overrides __init__ to add state but you need to skip it and initialize only the concrete base, a direct call gives you that control. This is an explicit decision, not a default.

Runtime Cost and Maintainability

Creating a super() proxy is cheap. The proxy performs an attribute lookup through the MRO, and the resulting method call dominates the cost. There is no measurable overhead worth optimizing in typical application code. The proxy object itself is small and ephemeral, and Python's attribute lookup is already the dominant operation in any method dispatch.

The maintainability benefit is more significant. Using super() means the call site does not reference the parent class by name. If the inheritance structure changes, the call continues to resolve correctly through the MRO. Direct parent-class calls, by contrast, create a hard dependency on the class name and break cooperative chains. When a hierarchy grows to include mixins or additional base classes, code written with super() adapts without edits, while direct calls require manual updates at every call site.

python super method call: Practical Usage and Code Examples | RYUSLOG DEV