The super() function returns a proxy object that delegates method calls to a parent or sibling class in the inheritance hierarchy.
Dynamic Dispatch & MRO: Using super() avoids hardcoding base class names, ensuring maintainable code during refactoring and enabling cooperative multiple inheritance. Rather than simply accessing the immediate parent, super() dynamically resolves methods along the class's Method Resolution Order (MRO). For related object-oriented techniques, explore class inheritance, @classmethod, and issubclass().
In modern Python 3, super() can be called with zero arguments inside methods, or with explicit parameters:
proxy = super()
proxy = super(type[, object_or_type])
| Syntax Form | Parameters | Behavioral Details |
|---|---|---|
super() |
Zero arguments (Python 3+) | Automatically infers the enclosing class and the current instance (self) from method frame context. |
super(Type, obj) |
Explicit class and instance | Binds delegation starting at the next class after Type in obj.__class__.__mro__. |
| Return Value | super proxy object |
A proxy forwarding attribute lookups along the linearized MRO chain. |
__init__ 🔝The most common usage is delegating initialization from a derived class to its parent:
class Animal:
home = "Jungle"
def __init__(self, name: str, height: float):
self.name = name
self.height = height
def legs(self):
print("has four legs")
class Wild(Animal):
def __init__(self, name: str, weight: int):
# Delegate name and default height=5.0 to parent Animal
super().__init__(name, 5.0)
self.weight = weight
tiger = Wild("Tom", 300)
print("Weight:", tiger.weight)
print("Height:", tiger.height)
tiger.legs()
Output:
Weight: 300
Height: 5.0
has four legs
super() Delegation 🔝While calling ParentClass.__init__(self, ...) works in simple single inheritance, super() provides crucial architectural advantages:
| Aspect | Direct Call: Parent.__init__(self) |
Proxy Delegation: super().__init__() |
|---|---|---|
| Base Class Renaming | Fragile: requires changing class name in child | Robust: child code remains untouched |
| Multiple Inheritance | Causes duplicate calls (Diamond problem) | Linearizes calls cleanly via C3 MRO |
| Cooperative Classes | Bypasses sibling cooperations | Calls sibling mixins in proper MRO sequence |
Python calculates a linear search path for every class called the Method Resolution Order (MRO) using the C3 Linearization algorithm. You can inspect it via __mro__:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
# Inspect the calculated MRO list
mro_names = [cls.__name__ for cls in D.__mro__]
print("Class D MRO:", mro_names)
Output:
Class D MRO: ['D', 'B', 'C', 'A', 'object']
When multiple inheritance forms a diamond shape, calling base classes directly causes the root class to initialize multiple times. super() ensures every class initializes exactly once:
class Root:
def __init__(self):
print("Root initialized")
class Left(Root):
def __init__(self):
print("Left entering")
super().__init__()
print("Left exiting")
class Right(Root):
def __init__(self):
print("Right entering")
super().__init__()
print("Right exiting")
class Bottom(Left, Right):
def __init__(self):
print("Bottom entering")
super().__init__()
print("Bottom exiting")
b = Bottom()
Output:
Bottom entering
Left entering
Right entering
Root initialized
Right exiting
Left exiting
Bottom exiting
super() is not limited to __init__. You can extend any parent method by wrapping or post-processing its return value:
class Notifier:
def send(self, msg: str) -> str:
return f"Sent: {msg}"
class LoggedNotifier(Notifier):
def send(self, msg: str) -> str:
original = super().send(msg)
return f"[LOGGED] {original}"
n = LoggedNotifier()
print(n.send("System reboot"))
Output:
[LOGGED] Sent: System reboot
Compose reusable functionality across independent classes using mixins:
class JsonMixin:
def to_dict(self) -> dict:
# Safely delegate up MRO
data = super().to_dict() if hasattr(super(), "to_dict") else {}
data.update({k: v for k, v in self.__dict__.items() if not k.startswith("_")})
return data
class User(JsonMixin):
def __init__(self, username: str):
self.username = username
u = User("alice")
print("Mixin serialized:", u.to_dict())
Output:
Mixin serialized: {'username': 'alice'}
*args, **kwargs)Ensure robust cooperative chaining by accepting and passing variable keyword arguments:
class BaseConfig:
def __init__(self, env: str = "prod", **kwargs):
super().__init__(**kwargs)
self.env = env
class DatabaseConfig(BaseConfig):
def __init__(self, port: int = 5432, **kwargs):
super().__init__(**kwargs)
self.port = port
cfg = DatabaseConfig(port=3306, env="staging")
print("Config:", cfg.env, cfg.port)
Output:
Config: staging 3306
super().__init__(), the chain terminates prematurely, leaving remaining MRO classes uninitialized!
Practice MRO linearization, cooperative diamonds, and mixin patterns in our companion Jupyter Notebook:
Explore interactive code cells, inspect __mro__ lists, solve diamond problem hierarchies, and run self-validating assertions.
Run these unit checks directly in Python or your local environment to verify super() mechanics:
def test_super_mechanics():
class A:
def __init__(self, val: int):
self.val = val
class B(A):
def __init__(self, val: int, extra: str):
super().__init__(val)
self.extra = extra
b = B(42, "data")
assert b.val == 42
assert b.extra == "data"
print("All super() unit assertions passed! 100% verified.")
test_super_mechanics()
Output:
All super() unit assertions passed! 100% verified.
Binds methods to the class object rather than an individual instance.
Defines class-scoped utility functions receiving neither self nor cls.
Checks if a class derives from another class or tuple of classes.
Verifies whether an instantiated object is an instance of a specified class.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.