What is `super()`, and how does it interact with the MRO in multiple inheritance?
Quick Answer
`super()` returns a **proxy object** that resolves method calls against the **next class in the MRO after the current one**, not literally "my parent class." In single inheritance this looks identical to calling the parent directly; in multiple inheritance (cooperative mixins), it's what lets every class in the chain run its `__init__`/method in turn when each one calls `super().__init__()`.
Detailed Answer
super() is "next in MRO," not "my parent"
class LoggingMixin:
def __init__(self, *a, **kw):
print("LoggingMixin init")
super().__init__(*a, **kw)
class TimestampMixin:
def __init__(self, *a, **kw):
print("TimestampMixin init")
super().__init__(*a, **kw)
class Service(LoggingMixin, TimestampMixin):
def __init__(self):
print("Service init")
super().__init__()
Service()
# Service init
# LoggingMixin init
# TimestampMixin init
Service.__mro__ is [Service, LoggingMixin, TimestampMixin, object].
Inside LoggingMixin.__init__, super() doesn't call object.__init__
directly — it calls whatever is next after LoggingMixin in Service's
MRO, which happens to be TimestampMixin. This only works because every
mixin cooperatively calls super().__init__() and accepts *args, **kwargs
it doesn't recognize, passing them along.
Why "cooperative" multiple inheritance requires this discipline
If any mixin in the chain doesn't call super().__init__(), the chain
breaks and classes later in the MRO never get initialized. This is why
well-designed mixins always call super() even though, read in isolation,
it's not obvious what "next" even refers to — it depends on the final
class that combines them, which the mixin author can't know in advance.
Single inheritance: looks simple, same mechanism
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # calls Animal.__init__
self.breed = breed
Here super() happens to resolve to Animal because the MRO is just
[Dog, Animal, object] — but it's the same "next in MRO" lookup, not
special-cased single-inheritance syntax.
super() vs calling the parent class explicitly
class Dog(Animal):
def __init__(self, name, breed):
Animal.__init__(self, name) # works, but bypasses MRO
self.breed = breed
Animal.__init__(self, name) calls Animal directly and skips the MRO
entirely — in multiple inheritance this can call a class twice or skip
classes, so super() is preferred whenever cooperative multiple
inheritance (mixins) is in play.
Interview-ready summary: super() doesn't mean "my parent class" — it
means "the next class after me in this instance's MRO." That indirection
is precisely what allows mixins to be composed in any order and still all
run, as long as every class in the chain forwards the call with
super().__init__(*args, **kwargs).