---
title: 'Inheritance and Method Resolution Order'
source: 'https://academia.sh/en/courses/python-object-and-types/inheritance-and-method-resolution-order'
course: 'Object-Oriented Python and Types'
language: en
updated: '2026-08-17T18:10:30+00:00'
license: 'CC BY-SA 4.0'
---

# Inheritance and Method Resolution Order

The body of four capabilities spreads across three separate classes in inheritance, and `Document` writes 0 of them in its own body; `Wrapper` writes all 4 in composition. The `format` method, whose body is written in `Record`, prints `<Signed>`.

The previous lesson showed that in a single-class tree, the answer always comes from the
same place: lookup checks the instance's dict, moves to the class if it fails there, and
stops. The last measurement had already drawn the line — a class method whose body is
written in `Formatter`, called through `SubFormatter`, bound `SubFormatter` to `cls`. Where
the body sits and where the call is answered had already come apart.

This lesson enlarges that split and pays off the course's **second claim**. The same four
capabilities will be given in two separate designs: one an inheritance tree, the other
composition. Both designs produce the same results. The question is not the result: **which
class is each capability's body written in?** The question suits measurement, because its
answer comes from reading code, not opinion — a name either exists in a class's dict or it
does not.

## Same Four Capabilities, Two Designs

The inheritance side is made of four classes. `Record` carries two capabilities: `source`,
and `format`, which calls it. `Timed` derives from it and adds `stamp`. `Signed` also derives
from it, adds `signature`, and **rewrites** `source`. `Document` derives from both and its
body is empty.

The composition side is a single class. `Wrapper` derives from nothing; it holds two objects
inside itself and writes all four capabilities in its own body, passing each call through to
the object inside it.

Which class a capability's body is written in is told by the **method resolution order
(MRO)**. Every class carries such an order: a list, computed once at definition time, of
which classes a lookup walks and in what sequence. The `answering_class` function walks this
list and returns the first class carrying the name.

`Document`'s empty body is not a gap, it is the measurement itself. That body declares
exactly one thing: which classes it derives from, and **in what order**. The class's entire
behavior is born from this single declaration, and when the declaration changes, so does the
behavior — reorder the bases and the class answering `source` changes; add one line to
`Document`'s body and the answer shifts there. Composition has no such declaration;
`Wrapper`'s behavior is read only from its own body.

The measurement's assumptions:

- **CD16** — The four-class tree and `Wrapper` are the shared rig's defined form; the
  placement of capabilities has not been changed.
- **CD17** — The four measured capabilities are `source`, `format`, `stamp`, and `signature`;
  the measurement looks at the **class the body is written in**, not the call's **result**.
- **CD18** — The composition column is not a constant — it would come out the same even read
  from `Wrapper`'s own dict: all four names are written in that body.
- **CD19** — When the resolution order is printed, the list's last item (the common base of
  all classes) is trimmed; what is measured is the rig's own four classes.

```python
"""Who answers the call: inheritance and composition side by side."""


class Record:
    def source(self):
        return "Record"

    def format(self):
        return f"<{self.source()}>"


class Timed(Record):
    def stamp(self):
        return "time"


class Signed(Record):
    def source(self):
        return "Signed"

    def signature(self):
        return "signature"


class Document(Timed, Signed):
    pass


class Wrapper:
    """An object giving the same capability via composition: who answers is written explicitly."""

    def __init__(self):
        self.timed = Timed()
        self.signed = Signed()

    def source(self):
        return self.signed.source()

    def stamp(self):
        return self.timed.stamp()

    def signature(self):
        return self.signed.signature()

    def format(self):
        return f"<{self.source()}>"


def answering_class(cls, name):
    """Which class provides an attribute: the first one found during resolution."""
    for s in cls.__mro__:
        if name in s.__dict__:
            return s.__name__
    return None


CAPABILITIES = ("source", "format", "stamp", "signature")

k = {name: answering_class(Document, name) for name in CAPABILITIES}
b = {name: "Wrapper" for name in CAPABILITIES}

print(f"{'capability':<10s} {'answering in inheritance':>25s} {'answering in composition':>25s}")
for name in CAPABILITIES:
    print(f"{name:<10s} {k[name]:>25s} {b[name]:>25s}")
print()
print("resolution order: " + " -> ".join(s.__name__ for s in Document.__mro__[:-1]))
print(f"Document().format() -> {Document().format()} | Wrapper().format() -> "
      f"{Wrapper().format()}")
print(f"in inheritance, capability with body written in Document: "
      f"{sum(1 for a in CAPABILITIES if k[a] == 'Document')}/{len(CAPABILITIES)}; "
      f"in composition: {sum(1 for a in CAPABILITIES if b[a] == 'Wrapper')}/{len(CAPABILITIES)}")
```

```
capability  answering in inheritance  answering in composition
source                        Signed                   Wrapper
format                        Record                   Wrapper
stamp                          Timed                   Wrapper
signature                     Signed                   Wrapper

resolution order: Document -> Timed -> Signed -> Record
Document().format() -> <Signed> | Wrapper().format() -> <Signed>
in inheritance, capability with body written in Document: 0/4; in composition: 4/4
```

The last line is the claim itself. `Document` carries **0** of the four capabilities in its
own body; `Wrapper` carries all **4**. Reading `Document`'s body shows exactly one thing —
two base names; what the object can do is **not written** in that body. Reading `Wrapper`'s
body shows all four capabilities and who each call is passed to.

The left column shows the answer spread across three separate classes: `source` comes from
`Signed`, `format` from `Record`, `stamp` from `Timed`. `source` is written in two classes at
once — both `Record` and `Signed` — but since `Signed` comes first in resolution order, it
gives the answer. The body in `Record` is still there and is never called.

The subtler point sits in the second row. `format`'s body is written in **`Record`**, and
that body writes `self.source()`. Since the body sits inside `Record`, it might seem it would
call the `source` there; **it does not.** The call is made through `self`, and `self` is a
`Document` instance — the lookup runs in `Document`'s order and stops at `Signed`. The result
comes out `<Signed>`. **Reading a body does not tell you what it will produce;** that is told
by the resolution order of the object at call time. `Wrapper` gives the same result, but
there the destination of the pass-through is written inside the body itself.

The Programming Paradigms course established preferring composition over inheritance as a
**design principle**, alongside the fragile base class problem; **that discussion is not
repeated here.** What is measured here is not the principle's justification but its
measurable counterpart: **where** the body is written. 0/4 against 4/4 is the same behavior's
two different readability costs.

The two numbers point in opposite directions, and the choice is made exactly here. Adding a
fifth capability costs **0** lines in inheritance — a method written on `Timed` shows up on
`Document` automatically. In composition it costs **1** line, a pass-through written on
`Wrapper`. In exchange, answering "who answers this call" costs reading four bodies and their
order in inheritance, one body in composition. Inheritance shortens the write and lengthens
the read; composition does the reverse.

The split has an object side too. A `Document` instance is **one** object; all four classes'
bodies operate on a single dict, and any state `Record` holds sits there **once**. A
`Wrapper` instance, by contrast, is **three** objects — itself and the two built in
`__init__` — and state from `Record` is held **separately** in each. Both designs answer the
same calls, but store state in a different number of places: sharing is unavoidable in
inheritance, and happens in composition only if written explicitly.

## How the Order Is Built

The order `Document -> Timed -> Signed -> Record` is not arbitrary; it is produced by two
rules: every class comes **before** its own bases, and bases are kept in the **order they are
written** in the class body. `Record` sits last because it is the base of both `Timed` and
`Signed`, and both have to come before it.

These two rules cannot always be satisfied together. When they cannot, the class **cannot be
built**, and the error is raised not while an instance is built but while the `class`
statement itself runs.

- **CD20** — Four base orders are built from the same three classes; the built class itself
  is dropped from the output, only the order after it is printed.
- **CD21** — For an order that fails to build, the **exception's name** is recorded with an
  exclamation prefix.
- **CD22** — The classes are the previous block's definitions; the tree is unchanged.

```python
def try_building(bases):
    """Can a class be built with the given base order?"""
    try:
        s = type("Trial", bases, {})
    except TypeError as e:
        return f"!{type(e).__name__}"
    return " -> ".join(t.__name__ for t in s.__mro__[1:-1])


print(f"{'base order':<22s} {'order after the built class':<34s}")
for bases in ((Timed, Signed), (Signed, Timed),
              (Timed, Record), (Record, Timed)):
    name = "(" + ", ".join(t.__name__ for t in bases) + ")"
    print(f"{name:<22s} {try_building(bases):<34s}")

print()
print(f"Record's position in Document's order: "
      f"{Document.__mro__.index(Record)} (last of the four classes)")
print(f"Timed's direct base: "
      f"{', '.join(t.__name__ for t in Timed.__bases__)}")
print(f"class right after Timed in Document's order: "
      f"{Document.__mro__[Document.__mro__.index(Timed) + 1].__name__}")
```

```
base order             order after the built class       
(Timed, Signed)        Timed -> Signed -> Record         
(Signed, Timed)        Signed -> Timed -> Record         
(Timed, Record)        Timed -> Record                   
(Record, Timed)        !TypeError                        

Record's position in Document's order: 3 (last of the four classes)
Timed's direct base: Record
class right after Timed in Document's order: Signed
```

**Three** of four orderings build, **one** fails. The first two lines show base-writing order
directly decides the result: swap the places and the class answering `source` changes too.
The fourth line draws the boundary — with `Record` written first and its derivative `Timed`
second, the rule "every class comes before its bases" cannot be satisfied, and the class
cannot be built at all. This is not a runtime error; it is raised **at definition time**. The
order is computed once per class and stored on it, not rebuilt on every access. Lookup cost,
for this reason, depends not on the tree's shape but on how far into the order a name is
found.

The last three lines close the question of where the first table's order came from. `Record`
is `Timed`'s **direct** base; despite that, the class coming right after `Timed` in
`Document`'s order is not `Record`, it is **`Signed`**. The order does not follow a single
branch straight up the tree; once it passes a class it does not jump to that branch's base —
it **pushes the common base to the very end.**

The reason sits in the first measurement. If the order were
`Document -> Timed -> Record -> Signed`, `source` lookup would stop at `Record`, and the body
`Signed` writes would never be found — a method one sibling rewrites lost because of the
other sibling's base. Pushing the common base to the end prevents this: the base is only
asked once **all** its derivatives have been tried. The order's two rules are therefore not a
style choice, they are the condition for multiple inheritance to make sense at all.

## `super()` Goes to the Order, Not the Ancestor

The most visible consequence of this is the `super()` call. Its name reads like an "ancestor
class" call, but that is not what it does: `super()` goes to the class **right after** the
one the body is written in, **in the calling object's resolution order**. The body stays the
same, the order changes, the destination changes.

- **CD23** — Four new classes are built for this measurement; the rig's tree is untouched.
- **CD24** — All four classes write the same name, and its body is a single line: put its own
  name on the list, and continue the chain via `super()`. The base class ends the chain.
- **CD25** — What is measured is not the call's result but the **sequence of bodies that
  ran**; the number is the body count.
- **CD26** — The second table reads `LeftLayer`'s body through two separate instances; the
  body is one, the instances are two different classes.

```python
class Layer:
    def label(self):
        return ["Layer"]


class LeftLayer(Layer):
    def label(self):
        return ["LeftLayer"] + super().label()


class RightLayer(Layer):
    def label(self):
        return ["RightLayer"] + super().label()


class Combined(LeftLayer, RightLayer):
    def label(self):
        return ["Combined"] + super().label()


def next_class(cls, body):
    """Where does the super() call written in class 'body' go?"""
    m = cls.__mro__
    return m[m.index(body) + 1].__name__


print(f"{'instance class':<14s} {'bodies run':>10s}  chain")
for s in (LeftLayer, Combined):
    chain = s().label()
    print(f"{s.__name__:<14s} {len(chain):>10d}  {' -> '.join(chain)}")

print()
print(f"{'body written in':<16s} {'instance class':<16s} super() goes to")
for s in (LeftLayer, Combined):
    print(f"{'LeftLayer':<16s} {s.__name__:<16s} {next_class(s, LeftLayer)}")
```

```
instance class bodies run  chain
LeftLayer               2  LeftLayer -> Layer
Combined                4  Combined -> LeftLayer -> RightLayer -> Layer

body written in  instance class   super() goes to
LeftLayer        LeftLayer        Layer
LeftLayer        Combined         RightLayer
```

`LeftLayer`'s body is **identical** in both measurements and a single line. On a `LeftLayer`
instance, the `super()` call in that line goes to `Layer`, and the chain ends at **2**
bodies. On a `Combined` instance, the same line goes to **`RightLayer`** — a class that is
not `LeftLayer`'s base and whose name does not even appear in its body — and the chain ends
at **4** bodies.

`RightLayer` never being skipped is the whole point of this arrangement: in multiple
inheritance, every class's body runs **exactly once**, and the common base runs **once** at
the very end. For the chain to work, every link has to call `super()`; once one class stops
calling it, every body after it silently drops out. Setup methods follow the same chain:
when `super().__init__(...)` is not written inside `__init__`, the intermediate classes'
setup bodies never run at all, and the gap only surfaces once a missing field is read.

The chain's second condition is signatures. Since a body does not know which class `super()`
will land on, every body sharing a name has to be written with parameters that **accept each
other**. Whoever wrote `LeftLayer`'s body may never have seen `RightLayer`; even so, the two
run side by side in the same call. Composition carries no such implicit contract —
`Wrapper.__init__` builds the two inner objects explicitly, by name and by argument, and the
destination is written in the body, not in an order.

The design-side conclusion follows from this. Reading a body correctly is not enough to know
the class it sits in; **which object the call came from** also has to be known. Composition
has no such unknown: the call inside `Wrapper.format`'s body goes to `self.signed`, and that
name is written inside the body itself. Inheritance gives a short write and hides who
answers; composition demands a longer write and writes who answers.

What is hidden here is not unreadable, though. Resolution order is not a guess, it is a list
sitting on the class and readable at any time; the `answering_class` function is three lines
walking that list. A capability's body does not have to be traced by eye through the class
tree — the order can be asked directly. Inheritance's cost is not that information
**disappears**, it is that reading the call requires asking for it separately.

## Summary

- Which class answers a capability is decided by the **method resolution order**: if a name
  is written in more than one class, whichever comes first in the order wins, and the others
  are never called.
- `Document` carries **0** of the four capabilities in its own body, and the answer comes
  from three separate classes; `Wrapper` writes all **4** of the same four capabilities in
  its own body.
- `format`'s body is written in `Record`, but since the call inside it resolves through
  `self`, the result comes out **`<Signed>`**: reading a body does not tell you its result.
- Resolution order is built by two rules — every class before its bases, bases in the order
  they are written. **Three** of four base orderings build; the **one** that violates the
  rule falls with a `TypeError` at definition time.
- `super()` does not go to the base of the class the body is written in — it goes to
  **whatever comes after that class in the instance's order**: the same `LeftLayer` body goes
  to `Layer` in one instance and to `RightLayer` in another, and the chain runs **2** versus
  **4** bodies.

## Next Step

Up to here, every name was assumed visible from outside: `Document`'s instance `source` and
the `signed` object `Wrapper` holds inside itself could both be read the same way. But
`signed` is `Wrapper`'s internal arrangement; code touching it from outside binds itself to a
detail `Wrapper` might change tomorrow. This is another place the two designs part ways: in
inheritance every base class's name passes to the derivative as is; in composition, what is
exposed outward is written name by name in the body. The next lesson measures how Python
declares this distinction: does an underscore in front of a name actually **block** access,
what does a double underscore change, and if it does not block access, does the declaration
have any measurable effect at all?
