Understanding Python super() and Method Resolution Order (MRO) with Advanced Examples
This article explains how Python's super() works by invoking the next class in the Method Resolution Order (MRO), demonstrates MRO behavior with multiple inheritance examples, and presents advanced techniques such as metaclasses, descriptors, class decorators, __slots__, and singleton patterns.
Introduction
In Python, super() does not simply call a parent class method; it calls the method of the next class in the Method Resolution Order (MRO), which defines the order Python searches for methods in a class hierarchy.
MRO Mechanics
Python's MRO follows the C3 linearization algorithm, a depth‑first left‑to‑right strategy that guarantees each class appears only once and that the order of multiple base classes is deterministic.
Example
Assume the following class hierarchy:
class A:
def do_something(self):
print("A doing something")
class B(A):
def do_something(self):
print("B doing something")
super().do_something()
class C(A):
def do_something(self):
print("C doing something")
super().do_something()
class D(B, C):
def do_something(self):
print("D doing something")
super().do_something()Class D inherits from B and C , both of which inherit from A . When D.do_something() is called, super().do_something() follows the MRO and invokes the next class's method.
Viewing the MRO
You can inspect a class's MRO via the __mro__ attribute:
print(D.__mro__)
# Output: (
,
,
,
,
)Calling the Next Method in the MRO
Inside D.do_something() , super().do_something() calls B.do_something() (the next class in D's MRO), not A directly.
Complete Code
class A:
def do_something(self):
print("A doing something")
class B(A):
def do_something(self):
print("B doing something")
super().do_something()
class C(A):
def do_something(self):
print("C doing something")
super().do_something()
class D(B, C):
def do_something(self):
print("D doing something")
super().do_something()
d = D()
d.do_something()
# Output:
# D doing something
# B doing something
# C doing something
# A doing somethingAdvanced Examples
Metaclass for Dynamic Class Creation
class AutoStrMeta(type):
def __new__(cls, name, bases, attrs):
attrs['__str__'] = lambda self: f"{name}({', '.join(f'{k}={v}' for k, v in vars(self).items())})"
return super().__new__(cls, name, bases, attrs)
class MyClass(metaclass=AutoStrMeta):
def __init__(self, x, y):
self.x = x
self.y = y
obj = MyClass(10, 20)
print(obj) # Output: MyClass(x=10, y=20)Descriptor Protocol for Attribute Validation
class PositiveInteger:
def __init__(self, initial_value=None):
self.value = initial_value
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if not isinstance(value, int) or value < 0:
raise ValueError("Value must be a positive integer.")
self.value = value
class MyClass:
count = PositiveInteger()
def __init__(self, count):
self.count = count
obj = MyClass(10)
print(obj.count) # Output: 10
obj.count = -5 # Raises ValueErrorClass Decorator for Automatic Subclass Registration
registry = []
def register_class(cls):
registry.append(cls)
return cls
@register_class
class MyClass1:
def method(self):
return "MyClass1"
@register_class
class MyClass2:
def method(self):
return "MyClass2"
def create_instance(index):
return registry[index]()
instance1 = create_instance(0)
instance2 = create_instance(1)
print(instance1.method()) # Output: MyClass1
print(instance2.method()) # Output: MyClass2Using __slots__ and __getstate__/__setstate__ for Serialization Optimization
import pickle
class MyClass:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
def __getstate__(self):
return {'x': self.x, 'y': self.y}
def __setstate__(self, state):
self.x = state['x']
self.y = state['y']
obj = MyClass(10, 20)
serialized = pickle.dumps(obj)
deserialized = pickle.loads(serialized)
print(deserialized.x) # Output: 10
print(deserialized.y) # Output: 20Singleton Pattern via __new__ and __init__
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
if not hasattr(self, '_initialized'):
self.value = value
self._initialized = True
singleton1 = Singleton('first')
singleton2 = Singleton('second')
print(singleton1.value) # Output: first
print(singleton2.value) # Output: first
print(singleton1 is singleton2) # Output: TrueThese examples illustrate how super() follows the MRO to ensure consistent method resolution in multiple inheritance scenarios, and they demonstrate several advanced Python techniques for dynamic class creation, attribute validation, registration, memory optimization, serialization, and singleton implementation.
Test Development Learning Exchange
Test Development Learning Exchange
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.