2.1.10 · D1OOP Fundamentals

Foundations — Multiple inheritance — Python's C3 linearization algorithm

3,955 words18 min readBack to topic

Before you can read the parent note, you need to see what a class is, what "inherit" means as a picture, what a list and its head/tail are, and what an "order" even is. This page builds every one of those from nothing.


0. What is a class, really?

Picture a rubber stamp. The stamp is the class; every ink mark it leaves on paper is an instance. The shape carved into the stamp — the lines it will print — is the set of methods and attributes.

Figure — Multiple inheritance — Python's C3 linearization algorithm
Figure s01 — On the left, a single blue box labelled "class (stamp)". Yellow arrows fan out to three small green boxes labelled "instance #1, #2, #3". The picture says: one blueprint produces many stamped copies. Look at how every arrow starts from the same stamp — that shared origin is the class.

A method is just a function that lives inside a class. When you write d.speak(), Python must find a method named speak somewhere. Where it looks — and in what order — is the entire subject of Method Resolution Order.


1. Inheritance — the arrow "is a kind of"

The picture is an arrow pointing from child up to parent. Read the arrow as "is a kind of": a Dog is a kind of Animal.

Figure — Multiple inheritance — Python's C3 linearization algorithm
Figure s02 — A green box "Dog (child)" sits at the bottom; a blue box "Animal (parent)" sits above it. A red arrow points UP from Dog to Animal, labelled 'is a kind of'. The yellow caption reminds you that a search starts at Dog and only climbs the arrow to Animal if Dog itself lacks the method. Notice the arrow points from child to parent, but the search travels the same direction only after checking the child first.


2. The root class object — where every arrow ends

Picture the top of a family tree: no matter which cousin you start from, all the branches eventually meet at one shared ancestor. That shared ancestor is . Because it is everyone's parent, the golden rule (child before parent) forces to always sit last in any search order.


3. Multiple parents & the diamond shape

If B and C both point up to the same A, the four arrows form a diamond:

        A
       / \
      B   C
       \ /
        D

Figure — Multiple inheritance — Python's C3 linearization algorithm
Figure s03 — The classic diamond: blue box A on top, two green boxes B and C in the middle, yellow box D at the bottom. Red arrows show D reaching A by two separate roads (labelled "road 1" through B and "road 2" through C). The whole problem of this topic lives in this picture: two roads to A means we must decide when to place A so we neither visit it twice nor visit it too early.


4. Lists, heads, and tails

The algorithm works entirely on lists of classes, so you must be fluent in three words. (Now that means the root class, the sample list below reads as "first B, then A, then object.")

For the list :

  • head
  • tail ← note: and are both "in the tail".

Figure — Multiple inheritance — Python's C3 linearization algorithm
Figure s04 — The list [B, A, O] drawn as three boxes in a row. A yellow arrow points down to the first box marked "HEAD". A red bracket underlines the remaining two boxes marked "TAIL = everything after the head". The lesson to read off the picture: only the tail (red) blocks a candidate; the head slot (yellow) is exactly where we want the next class to come from.

The "+" operator: gluing a class onto the front of a list

Picture sliding one new box into the leftmost slot of a row of boxes, pushing nothing else around — they all just shift one place right. That single leftmost box is now the head; everything you had before becomes the tail.


5. Watch the merge actually run

Before the abstract formula, let us weave three lists into one by hand so the head/tail rule becomes concrete. This is the merge from the diamond class D(B, C) where B(A) and C(A). We already know the parents' orders: , , and the plain parent list is .

Figure — Multiple inheritance — Python's C3 linearization algorithm
Figure s05 — Four horizontal rows, one per merge step, showing the three lists shrinking left-to-right. In each row the class being emitted is circled in green and struck out of every list; a red X marks any candidate rejected because it sits in a tail. Reading top to bottom you literally watch the diamond flatten into the single green line D, B, C, A, O at the bottom — this is the "weaving" the diamond picture only hinted at.

We start with these three lists (D itself is already placed out front):

Putting back in front with the prepend operator: . The diamond became one clean line, appears exactly once, and it lands after both and — precisely the child-before-parent rule paying off.


6. What "order" means: an ordering with rules

Turning a branching shape (the diamond) into one straight line while obeying constraints is exactly a Topological sort — an ordering that respects "this must come before that" arrows. C3 is a topological sort with two extra tie-breaking rules (keep written parent order; stay monotonic).

Local precedence, shown

You wrote class D(B, C), so in any tie between B and C, B must win — because you listed it first. Watch it happen in Step 1 above: both B and C were sitting at the front of some list, but the merge always tests list 1 first (which starts with B, the first-written parent). That is why the output is and never . The plain parent list at the end of the formula is the guardrail that enforces this: if the merge ever tried to emit C before B, then C would be blocked because B is still in the tail of .

Monotonicity, shown

Monotonicity means an order, once established in a parent, is never flipped in a child. In , B comes before A. Any subclass of B — including D — must keep B before A. Check the final answer : yes, B still precedes A.

Why does flipping break things? Suppose some algorithm produced — putting the parent A before its child B. Then calling a method that B overrides would find A's old version first. The child's improvement would be silently ignored. Monotonicity is the promise that this never happens: refinements always stay in front of what they refine.


7. Building the master formula, piece by piece

The parent page hands you the formula finished. Here is why each piece is there, assembled from scratch.

Piece 1 — "". A child is always searched before its parents, so the line must start with itself, glued on with the prepend operator we defined in Section 4. That is the leading C +.

Piece 2 — "the parents' own orders." Each parent already knows how it wants its ancestors ordered: that is . If we ignored these we could contradict a parent's internal order (breaking monotonicity). So we must feed every into the weave.

Piece 3 — "the plain list ". Why one more list? The parents' individual orders don't say anything about how the parents rank against each other. Without a tiebreaker the merge could emit C's parents in the wrong order. Adding the raw written order forces local precedence: it keeps B ahead of C because in that list C sits in the tail behind B. This is the exact guardrail we saw in the local-precedence example.

Weaving all three pieces with the head/tail merge gives:

Every symbol here you have now met: (a class), (its parents), (an order), (front-prepend), (the weaver), (the root). The topic uses no notation beyond these.

Worked example — a plain single-inheritance chain

Let us run the formula end-to-end on the simplest possible case, class B: pass then class A(B): pass, so you see the base case and merge cooperate without any diamond.


Sometimes the constraints genuinely contradict each other and no valid line exists. C3 must detect this and refuse, rather than invent a wrong order.

Concretely, imagine the merge has reduced to two lists and . Test the head of list 1, : it sits in the tail of list 2 (which is ) → blocked. Test the head of list 2, : it sits in the tail of list 1 (which is ) → blocked. Both heads are blocked, no list remains to try — deadlock detected, so C3 raises the TypeError.

Picture the deadlock: X is waiting behind Y in one list, and Y is waiting behind X in another. Each is politely holding the door for the other, forever. That standstill is exactly the "no good head" state, and detecting it is C3's built-in safety check.


9. Why super() needs all of this


Prerequisite map

Class = blueprint of methods

Inheritance = is a kind of arrow

Multiple parents

Diamond shape = two roads up

Root class object = O

Ordering with rules

Base case L of O = O

Lists with head and tail

Merge = head not in any tail

Prepend operator plus

Topological sort

C3 Linearization

Method Resolution Order

super chooses next in MRO

Each foundation on the left feeds the C3 algorithm; C3 produces the MRO; the MRO drives super().


Equipment checklist

Give a class B inheriting from A, which is searched first for a method?
B itself (child before parent), then A if not found.
In the list [C, A, O], what is the head and what is the tail?
Head is C; tail is [A, O].
Is a class that sits at the front of a list "in a tail"?
No — tail means positions after the front, so the front element is not in that tail.
What does object (written O) represent, and where does it sit in any MRO?
The root class every Python class inherits from; it always ends the MRO.
What does the + operator mean in ?
Prepend — glue class C onto the front of the following list as its new head.
What is the base case of the C3 recursion?
— the root class linearizes to just itself.
What is for class A(B) where B has no explicit parent?
.
In the merge of [B,A,O], [C,A,O], [B,C], why is C emitted before A?
When A is tested it is in the tail of [C,A,O], so it is rejected; the next list's head C is in no tail, so C goes first.
What is the final merge result of [B,A,O], [C,A,O], [B,C]?
[B, C, A, O], giving L[D] = [D, B, C, A, O].
Why does the master formula include the plain list [P₁,…,Pₙ]?
It is the tiebreaker that enforces local precedence — keeping written parent order between the parents themselves.
What does monotonicity forbid, and why does it matter?
Flipping a parent-established order in a subclass; flipping could search a parent before its child and ignore the child's override.
When does C3 raise a TypeError, and what does it say?
When no list's head is a good head (every candidate is blocked in some tail); it says "Cannot create a consistent method resolution order (MRO)".
What do MRO and C3 each stand for?
MRO = Method Resolution Order (the search line); C3 = the algorithm that builds it, named for its three consistency guarantees.
What does super() actually refer to?
The next class in the MRO line, which may be a sibling, not the literal parent.

Connections

  • 2.1.10 Multiple inheritance — Python's C3 linearization algorithm (Hinglish)
  • Method Resolution Order
  • The Diamond Problem
  • Single inheritance
  • super() and cooperative inheritance
  • Topological sort
  • Mixins
  • Composition vs Inheritance