MRO and multiple inheritance: C3, the diamond, and where super() really goes
The method lookup order is not “left to right, depth first” but the C3 linearization, and it follows from a single merge rule. The same rule gives both the diamond, where the common ancestor goes to the tail, and the TypeError with which Python refuses to create a class. And super() goes not to the parent but to the next class in the instance's MRO — so the same method calls different neighbours in different hierarchies.
Full technical treatment
TL;DR
The method lookup order is a list, and every class has its own: __mro__.
Under single inheritance it is obvious — the class, its parent, its
grandparent. Under multiple inheritance it is built by the C3 algorithm,
which has one rule: take the head of the first list if it is not in the tail of
any other, otherwise the head of the next one. Two facts follow from that rule and are worth knowing by heart. In
the diamond D(Left, Right) the common ancestor goes to the tail:
D, Left, Right, Base, object, and a method Left lacks is taken from
Right, not from Base. And if the bases demand opposite orders, there is no
good head, and Python refuses to create the class: TypeError: Cannot create a consistent method resolution order (MRO).
Beyond that is what separates knowing from having read.
super() goes not to the parent but to the next class in the instance's
MRO. The method Left.greet is written once, yet for a Left instance its
super() goes to Base, and for a D instance to Right, which Left does
not inherit and knows nothing about. That is why in a diamond where everyone
calls super() each method runs exactly once. And that is why the chain
ends silently at a class that does not call super(): the neighbours are
in the MRO and never run, with no error. In CPython the length of the MRO
barely affects a repeated lookup: the type attribute cache answers in
10.8–11.9 ns for lengths from 2 to 51 (3.13.7); without the cache the lookup is
linear.
- how a class is declared and inherited from another;
- that a method the class lacks is looked up in its parent;
- that a class can inherit from several at once:
class D(Left, Right).
__mro__, the C3 linearization, a conflict in the order of bases;super()in a diamond, a cooperative__init__, the__class__cell, the type attribute cache.
Base: every class has a list its methods are looked up in
When you write obj.greet(), Python looks for greet not “somewhere in the
hierarchy” but along a specific list of classes, in order, and takes the first
match. The list sits on the class in the __mro__ attribute — the method
resolution order.
Under single inheritance it is obvious:
class Base:
def greet(self):
return ["Base"]
class Left(Base):
def greet(self):
return ["Left"] + super().greet()
print(Left.__mro__) # (Left, Base, object)The class, its parent, the parent's parent — and object at the end, which
everything inherits from. The Junior-level answer to “what is the MRO” is
exactly this: it is the order in which Python looks an attribute up across
the classes of a hierarchy; you can see it in Class.__mro__.
The question gets interesting when there is more than one parent, and especially when they share an ancestor — a diamond.
Mechanism 1: C3 — a merge with one rule
Intuition says “left to right, depth first”: first the whole Left branch
with its ancestors, then the whole Right branch. For the diamond that gives
D, Left, Base, Right, Base — and Base's method would win over Right's.
Python does not do that:
class Right(Base):
def greet(self):
return ["Right"] + super().greet()
class D(Left, Right):
def greet(self):
return ["D"] + super().greet()
print([c.__name__ for c in D.__mro__])
# ['D', 'Left', 'Right', 'Base', 'object']The common ancestor Base went to the tail, after both of its
descendants. Where that comes from is easiest to see by hand. C3 merges
several lists: the parents' orders — Left, Base, object for Left and
Right, Base, object for Right — and the list of bases itself, Left, Right. There is one rule: take the head of the first list if it is not in
the tail of any other list; otherwise try the head of the next one. The class
taken is struck out of every list, and the step repeats.
The script bench/mro/order.py does exactly that, prints the merge step by
step and checks the result against the interpreter's __mro__ — it matched on
seven hierarchies out of seven:
1) C3 step by step: class D(Left, Right), common ancestor Base
step 1: [Left Base object] [Right Base object] [Left Right] -> take Left
step 2: [Base object] [Right Base object] [Right] -> take Right
step 3: [Base object] [Base object] -> take Base
step 4: [object] [object] -> take object
by hand: ['D', 'Left', 'Right', 'Base', 'object']
D.__mro__: ['D', 'Left', 'Right', 'Base', 'object']
hand-written C3 matched __mro__ on 7 hierarchies of 7
The whole diamond is decided at the second step. Left's list comes first,
and its head is now Base — but Base sits in the tail of Right's list. It
cannot be taken until Right is. That is how the rule expresses what the order
must satisfy: a class comes before its ancestors (hence Base after both
descendants) and the order of the bases in the declaration is kept (hence
Left before Right).
Formally, the documentation writes the same thing in one line:
the linearization of C is the sum of C plus the merge of the linearizations of
the parents and the list of the parents
And the merge by the very rule carried out by hand above:
take the head of the first list, i.e L[B1][0]; if this head is not in the tail
of any of the other lists, then add it to the linearization of C and remove it
from the lists in the merge
This strictness has a name — monotonicity: if C1 comes before C2 in a
class's order, the same holds in the order of every subclass. Adding a subclass
cannot accidentally reorder the methods of its ancestors.
Mechanism 2: when there is no order, there is no class
The merge does not always succeed. Two classes with the opposite order of the same bases — and at some step there is no good head:
2) when there is no order
class Impossible(XY, YX) -> TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y
class Backwards(Base, Left) -> TypeError: Cannot create a consistent method resolution order (MRO) for bases Base, Left
merge by hand for Impossible(XY, YX):
step 1: [XY X Y object] [YX Y X object] [XY YX] -> take XY
step 2: [X Y object] [YX Y X object] [YX] -> take YX
step 3: no good head among X, Y
XY demands that X come before Y, YX the reverse. The head of each list
lies in the tail of the other, and Python does not pick a “reasonable”
option — it refuses:
it is impossible to construct the merge, Python 2.3 will refuse to create the class C and will raise an exception
.
The second example is shorter and looks innocent:
class Backwards(Base, Left). An ancestor is declared before its own
descendant — and C3 requires the descendant to come before the ancestor. The
fix is to swap the bases.
What matters is when this happens: when the class statement runs, that
is, when the module is imported. It never gets as far as a method call — the
module will not load.
Mechanism 3: super() is the next in the instance's MRO, not the parent
The familiar phrasing is “super() calls the parent's method.” The documentation says something else:
The search starts from the class right after the type.
“After” in which list? In the MRO of the object whose method was called,
not of the class where super() is written. The precise wording is this:
super(Left, obj) returns neither a class nor a call but a proxy — an
object through which attribute lookup goes along type(obj).__mro__, starting
with the class after Left. super() itself calls nothing: the call is the
.greet() on the method found through it. The short “super() goes to the
next in the instance's MRO” is a compressed form of this formula. That is what
the diamond's behaviour is made of:
3) where super() inside Left.greet goes
Left().greet() -> ['Left', 'Base']
D().greet() -> ['D', 'Left', 'Right', 'Base']
Left.greet is written once. For a Left instance, the next after Left is
Base. For a D instance, the next after Left is Right, which Left
does not inherit and knows nothing about. Each method in the diamond ran
exactly once, Base at the very end. That is a guarantee of the cooperative
chain, not of the language: it holds as long as all its participants call
super(). It is why super() is built this way: calling the parent by name
would run Base twice, once from each branch.
The flip side is that the chain relies on everyone calling super(). All
it takes is one class that does not call it:
4) one class does not call super()
Broken.__mro__: ['Broken', 'QuietLeft', 'Right', 'Base', 'object']
Broken().greet() -> ['Broken', 'QuietLeft']
Right and Base are in the MRO but never ran. No error, no warning: the
chain simply ended at QuietLeft. For Python this is not an error but an
ordinary method — and at the end of a chain it is exactly the intent:
Base.greet here does not call super(), and the chain ends there. It becomes
a bug when the hierarchy is meant to be cooperative and a class in its middle
drops out of the contract: if Right.__init__ opened a connection or
registered the object, that did not happen.
A cooperative __init__
For __init__ this gives a practical rule. No class in the chain knows what
comes after it — so none knows what arguments the next one needs. The documentation states the requirement like this:
Good design dictates that such implementations have the same calling signature
in every case
In practice that means keyword arguments: each class takes its own and passes the rest along.
class Named:
def __init__(self, *, name, **rest):
self.name = name
super().__init__(**rest)
class Sized:
def __init__(self, *, size, **rest):
self.size = size
super().__init__(**rest)
class Item(Named, Sized):
pass5) cooperative __init__: each takes its own, passes the rest on
Item.__mro__: ['Item', 'Named', 'Sized', 'object']
Item(name='box', size=3) -> name='box', size=3
the extra argument colour reached object -> TypeError: object.__init__() takes exactly one argument (the instance to initialize)
The last line is useful, not annoying: an argument nobody took reaches
object.__init__ and fails. A typo in an argument name is not swallowed
silently.
Deeper: how super() knows the class, and what a long MRO costs
super() with no arguments is not magic but a cell. Two levels are worth
telling apart here. The language contract, set by PEP 3135: super() with no
arguments works in a function declared in a class body and takes that class.
How exactly that is done is a CPython implementation detail, and the run shows
it: the compiler puts a hidden free variable __class__ into such a function —
if the function uses super() or __class__. The PEP puts it more broadly:
Every function will have a cell named class that contains the class object that the function is defined in
.
6) super() with no arguments: the __class__ cell
Left.greet.__code__.co_freevars: ('__class__',)
cell contents: Left
method without super() or __class__, co_freevars: ()
a function declared outside the class -> RuntimeError: super(): __class__ cell not found
co_freevars and the cell itself are what CPython shows, not a promise of the
language; code should rely on the contract. From it comes super()'s first
argument: the class the method is written in.
The second is self. The MRO is taken from type(self), and the search starts
after __class__. The same fact gives the limitation: a function declared
outside and attached to the class later has no cell, and super() with no
arguments fails in it. There you write the explicit form super(Left, self).
The length of the MRO barely affects a repeated call. A lookup along the list of classes is linear, and without the cache that shows: each level adds 3.31 ns on 3.13.7, and at length 51 an access costs 180.8 ns. But a type has an attribute cache: it answers a repeated access without walking the list at all.
PY 3.13.7 | best of 100 interleaved rounds of 20000 accesses
__mro__ length hot cache invalidated (cost of assignment)
2 10.8 ns 18.7 ns 26.7 ns
6 10.9 ns 29.2 ns 27.4 ns
21 11.0 ns 87.4 ns 26.8 ns
51 11.9 ns 180.8 ns 26.8 ns
per MRO level without cache: +3.31 ns
per MRO level with cache: +0.02 ns
at length 51: 181 ns without cache, 12 ns with
The cache is invalidated by assigning to the class — which is why the
comparison invalidates it on purpose on every iteration. In ordinary code
classes do not change after import, and the left column is what applies. The
cache is covered in detail in the article
on what happens on o.x.
How to answer in an interview
Short answer: the MRO is the order in which Python looks an attribute up
across the classes of a hierarchy; it lives in Class.__mro__ and is built by
the C3 algorithm. C3 puts a class before its ancestors and keeps the order of
the bases, so in the diamond D(Left, Right) the common ancestor goes to the
tail: D, Left, Right, Base, object. And super() is a proxy: lookup through
it goes along the instance's MRO, starting after the class where the method is
written, and that is not necessarily the parent.
That is enough to answer correctly. Beyond it is what you add if the interviewer digs.
If the interviewer digs deeper
Show the diamond and two outputs of the same method: for a Left instance its
super() goes to Base, for a D instance to Right. It is one line of code
and the most convincing proof that “super is the parent” is wrong. The
consequence follows: in a cooperative hierarchy everyone calls super(), and
arguments are passed as keywords with **rest, because the next in the chain
is not known in advance. Then each method in the chain runs exactly once — a
property of that contract, not a rule of Python.
About the refusal, be precise: TypeError: Cannot create a consistent method resolution order arises when the class is defined, that is, on import, for
example because an ancestor is declared before its descendant —
class C(Base, Child). The fix is to swap the bases.
And if cost comes up — in CPython the length of the MRO barely affects a repeated call: the hot type attribute cache answers in 10.8–11.9 ns for lengths from 2 to 51 (3.13.7). The linear growth, 3.31 ns per level, appears only when the cache is invalidated by assigning to the class.
Next they ask
How do you call a specific ancestor's method bypassing the MRO?
Explicitly: Base.greet(self). It is an ordinary function taken from the
class, with self passed by hand. But in a cooperative hierarchy this breaks
the main property of super() — Base will run twice if the super() chain
from the neighbouring branch reaches it too.
Why does super() work without arguments?
The compiler puts a __class__ cell holding the class the method is declared
in into a method that uses super(), and self is the method's first argument. A function
attached to the class from outside has no cell: super() in it fails with
RuntimeError: super(): __class__ cell not found, and you need the explicit
form super(Left, self).
What happens if one class in the chain does not call super()?
The chain ends there, and the classes after it in the MRO do not run —
silently. In the run, Broken(QuietLeft, Right) outputs
['Broken', 'QuietLeft']: Right and Base are in the MRO and never called.
Python treats this as an ordinary method; it becomes a problem only if the
hierarchy is meant to be cooperative.
Common misconceptions
super() calls the parent class's method
It returns a proxy, and lookup through it goes along the INSTANCE's MRO, starting after the class where the method is written. For Left().greet() that really is the parent Base, but for D().greet() the same super() in Left goes to Right — a neighbour Left does not inherit. The documentation: The search starts from the class right after the type
.
The MRO is a depth-first, left-to-right traversal
A depth-first traversal would give D, Left, Base, Right, Base for the diamond, and Base's method would win over Right's. C3 moves the common ancestor to the tail: D, Left, Right, Base, object. There is one rule — a class comes before all of its ancestors, and the order of the bases from the declaration is kept.
Python can always work out an order, somehow if need be
It cannot and does not try. If the bases demand opposite orders — class Impossible(XY, YX), or an ancestor before its descendant, class C(Base, Left) — Python fails with TypeError: Cannot create a consistent method resolution order (MRO) right when the class is defined.
A long hierarchy means a slow method call
In CPython with a hot type attribute cache, no: 10.8 ns at an MRO length of 2 and 11.9 ns at 51 (3.13.7). Linear growth exists only without the cache — 3.31 ns per level — and the cache is invalidated by assigning to the class, which ordinary code does not do after import.
If a class does not call super(), Python will say so
Silently — for Python it is an ordinary method. The classes after it are in the MRO and do not run: in the run Broken().greet() returned ['Broken', 'QuietLeft'], even though Right and Base are in its MRO.
Version history
| Version | Change | What it means for code |
|---|---|---|
| 2.3 | C3 becomes the MRO algorithm for new-style classes. The algorithm itself was not invented for Python — it comes from the Dylan language (Barrett et al., 1996). Simionato's document works through the 2.2 rule for new-style classes on an example and shows that it broke monotonicity — which is why it was replaced. | |
| 3.0 | PEP 3135: super() with no arguments. It rests on the __class__ cell the compiler puts into functions declared in a class body. Old-style classes are gone — C3 for everyone. | |
| 3.13 | The C3 refusal TypeError text is printed on one line. Verified: on 3.11.15 and 3.12.3 the message contains a line break — Cannot create a consistent method resolution and order (MRO) for bases X, Y come out as two lines. It does not affect behaviour, but it breaks tests that match the exception text as a whole. | |
| 3.14 | A metaclass that overrides mro() deprives its class of the type attribute cache — even if it returns exactly super().mro(). Measured by bench/attr-lookup/mro_override.py: 15.3 / 63.8 / 118.4 ns at lengths 2 / 21 / 51 against 6.5 / 6.4 / 6.4 ns for an ordinary class; on 3.13.7 there is no such effect. The C3 order itself did not change. |
Practice
Two exercises. Answer first, then check against the real output: in both, the correct answer comes from a run of the script rather than being assigned.
Practice · predict the output
class Base:
def hello(self):
print("Base")
class A(Base):
def hello(self):
print("A")
super().hello()
class B(Base):
def hello(self):
print("B")
super().hello()
class C(A, B):
pass
C().hello()Practice · estimate
Check yourself
class D(Left, Right), where Left and Right inherit Base. Left has no method m; Right and Base do. Whose m does D().m() call?
How it was measured
The numbers in this lesson come from these scripts. Each one opens right from here — together with the record of its run: what it ran on, what came out, and with what spread.
This is neither a retelling nor a separate text: everything below is taken from the article itself — its own summary, the section headings, the “actually” column and the version table. Which is why these theses cannot drift from the article.
The gist
- The method lookup order is a list, and every class has its own:
__mro__. Under single inheritance it is obvious — the class, its parent, its grandparent. Under multiple inheritance it is built by the C3 algorithm, which has one rule: take the head of the first list if it is not in the tail of any other, otherwise the head of the next one. Two facts follow from that rule and are worth knowing by heart. In the diamondD(Left, Right)the common ancestor goes to the tail:D, Left, Right, Base, object, and a methodLeftlacks is taken fromRight, not fromBase. And if the bases demand opposite orders, there is no good head, and Python refuses to create the class:TypeError: Cannot create a consistent method resolution order (MRO). - Beyond that is what separates knowing from having read.
super()goes not to the parent but to the next class in the instance's MRO. The methodLeft.greetis written once, yet for aLeftinstance itssuper()goes toBase, and for aDinstance toRight, whichLeftdoes not inherit and knows nothing about. That is why in a diamond where everyone callssuper()each method runs exactly once. And that is why the chain ends silently at a class that does not callsuper(): the neighbours are in the MRO and never run, with no error. In CPython the length of the MRO barely affects a repeated lookup: the type attribute cache answers in 10.8–11.9 ns for lengths from 2 to 51 (3.13.7); without the cache the lookup is linear.
In fact
- It returns a proxy, and lookup through it goes along the INSTANCE's MRO, starting after the class where the method is written. For
Left().greet()that really is the parentBase, but forD().greet()the samesuper()inLeftgoes toRight— a neighbourLeftdoes not inherit. The documentation: The search starts from the class right after the type. - A depth-first traversal would give
D, Left, Base, Right, Basefor the diamond, andBase's method would win overRight's. C3 moves the common ancestor to the tail:D, Left, Right, Base, object. There is one rule — a class comes before all of its ancestors, and the order of the bases from the declaration is kept. - It cannot and does not try. If the bases demand opposite orders —
class Impossible(XY, YX), or an ancestor before its descendant,class C(Base, Left)— Python fails withTypeError: Cannot create a consistent method resolution order (MRO)right when the class is defined. - In CPython with a hot type attribute cache, no: 10.8 ns at an MRO length of 2 and 11.9 ns at 51 (3.13.7). Linear growth exists only without the cache — 3.31 ns per level — and the cache is invalidated by assigning to the class, which ordinary code does not do after import.
- Silently — for Python it is an ordinary method. The classes after it are in the MRO and do not run: in the run
Broken().greet()returned['Broken', 'QuietLeft'], even thoughRightandBaseare in its MRO.
By version
- 2.3
- C3 becomes the MRO algorithm for new-style classes. The algorithm itself was not invented for Python — it comes from the Dylan language (Barrett et al., 1996). Simionato's document works through the 2.2 rule for new-style classes on an example and shows that it broke monotonicity — which is why it was replaced.<
- 3.0
- PEP 3135:
super()with no arguments. It rests on the__class__cell the compiler puts into functions declared in a class body. Old-style classes are gone — C3 for everyone.< - 3.13
- The C3 refusal
TypeErrortext is printed on one line. Verified: on 3.11.15 and 3.12.3 the message contains a line break —Cannot create a consistent method resolutionandorder (MRO) for bases X, Ycome out as two lines. It does not affect behaviour, but it breaks tests that match the exception text as a whole.< - 3.14
- A metaclass that overrides
mro()deprives its class of the type attribute cache — even if it returns exactlysuper().mro(). Measured bybench/attr-lookup/mro_override.py: 15.3 / 63.8 / 118.4 ns at lengths 2 / 21 / 51 against 6.5 / 6.4 / 6.4 ns for an ordinary class; on 3.13.7 there is no such effect. The C3 order itself did not change.<
What is covered
- Base: every class has a list its methods are looked up in
- Mechanism 1: C3 — a merge with one rule
- Mechanism 2: when there is no order, there is no class
- Mechanism 3: super() is the next in the instance's MRO, not the parent
- Deeper: how super() knows the class, and what a long MRO costs
- How to answer in an interview
- Next they ask
- Common misconceptions
- Version history
- Practice
- Check yourself
- How it was measured
Sources & further reading
4 SOURCES
- The Python 2.3 Method Resolution OrderOfficial documentation. The primary source for the rule that builds the MRO. Michele Simionato. The formula: “the linearization of C is the sum of C plus the merge of the linearizations of the parents and the list of the parents.” The merge rule: “take the head of the first list, i.e L[B1][0]; if this head is not in the tail of any of the other lists, then add it to the linearization of C and remove it from the lists in the merge.” And the refusal: “it is impossible to construct the merge, Python 2.3 will refuse to create the class C and will raise an exception.”https://docs.python.org/3/howto/mro.html
- Built-in Functions — superOfficial documentation. Where the claim that super() does not go to the parent comes from: “The search starts from the class right after the type,” with the example: “if __mro__ of object_or_type is D -> B -> C -> A -> object and the value of type is B, then super() searches C -> A -> object.” The rule for cooperative methods is on the same page: “Good design dictates that such implementations have the same calling signature in every case.”https://docs.python.org/3.14/library/functions.html#super
- PEP 3135 — New SuperPEP. Calvin Spealman, Tim Delaney, Lie Ryan; Final, Python 3.0. The document that introduced super() with no arguments, and the mechanism it rests on: “Every function will have a cell named __class__ that contains the class object that the function is defined in.” The run makes it precise: the cell appears in a function declared in the class body that uses super() or __class__; a method without them has none, and neither does a function attached to the class from outside — super() in it fails with a RuntimeError.https://peps.python.org/pep-3135/
- A Monotonic Superclass Linearization for DylanSource. Barrett, Cassels, Haahr, Moon, Playford, Withington, OOPSLA 1996. The paper where the C3 algorithm appeared — for the Dylan language, not for Python; Python adopted it in 2.3. Cited to name the property that makes C3 so strict: monotonicity — if C1 comes before C2 in a class's order, it does so in the order of every subclass too.https://doi.org/10.1145/236337.236343