MRO, mixins, and __init_subclass__
Reason about C3 linearization and cooperative super() instead of guessing.
MRO and cooperative super()
Every class has a method resolution order โ the C3-linearized list of
classes Python searches left-to-right for an attribute. Inspect it with
Cls.__mro__; reason about it, never memorize individual cases.
Three rules cover almost everything:
- Children come before parents.
- Base classes appear in the order written in the class statement.
- The linearization is consistent: no class appears before one of its own
bases in a way that would break the first two rules (C3 rejects such
hierarchies with a
TypeErrorat class-creation time).
super() does not mean "my parent". It means "the next class in the MRO
after me, for whoever is currently resolving". That is what makes mixin
cooperation work:
class ReprMixin:
def __repr__(self):
return f"{type(self).__name__}({', '.join(f'{k}={v!r}' for k, v in vars(self).items())})"
class JSONMixin:
def to_json(self):
import json
return json.dumps(vars(self))
class Point(ReprMixin, JSONMixin):
def __init__(self, x, y):
self.x, self.y = x, y
p = Point(1, 2)
print(p) # Point(x=1, y=2)
print(p.to_json()) # {"x": 1, "y": 2}
__init_subclass__ is the cooperative hook: the parent class runs code each
time a subclass is defined โ registering subclasses, validating class
attributes, or auto-generating methods. It runs without any metaclass:
class Registry:
_registry = {}
def __init_subclass__(cls, key=None, **kw):
super().__init_subclass__(**kw)
Registry._registry[key or cls.__name__.lower()] = cls
class RedisCache(Registry, key="redis"): pass
print(Registry._registry) # {'redis': <class RedisCache>}
Advanced habit: when a hierarchy grows weird, print
[c.__name__ for c in Cls.__mro__] and ask what each super() call forwards to.