Python MRO and Multiple Inheritance: Mixins and super()

Python orders multiple base classes with the C3 method resolution order. The diamond problem, why super calls the next class rather than the parent, and mixins.

  • Course: Python study plan
  • Module: Inheritance, protocols and duck typing
  • Kind: Lesson
  • Reading time: 14 min
  • Runtime: CPython 3.11

What is the MRO in Python?

The method resolution order (MRO) is the single linear order in which Python searches a class and its bases for an attribute. Python builds it by C3 linearisation: the class first, its bases left to right, a shared base only after every class that inherits from it, and object last. Class.__mro__ shows it, and super() follows it.

Lesson

A class may list several bases: class Report(JsonMixin, CsvMixin, Base). Python resolves which method wins with the C3 method resolution order, a single linear order over all the bases that respects each class's own base order and puts subclasses before their parents. super() follows that order rather than "the parent", which is what makes cooperative initialisation across several bases possible — and what makes super().__init__(**kwargs) the correct spelling in a mixin. This lesson shows the MRO, the diamond, cooperative super(), the mixin pattern that is the one everyday use of multiple inheritance, and the cases to avoid.

The MRO

class A:
    def who(self):
        return "A"

class B(A):
    def who(self):
        return "B>" + super().who()

class C(A):
    def who(self):
        return "C>" + super().who()

class D(B, C):
    def who(self):
        return "D>" + super().who()

D.__mro__          # (D, B, C, A, object)
D().who()          # 'D>B>C>A'

D(B, C) inherits from both, and both inherit from A — the diamond. C3 linearisation gives D, B, C, A, object: D first, then its bases left to right, with A placed after every class that inherits from it, and object last. Every class appears once. super() in B therefore calls C.who, not A.who, because C is next in D's MRO. super() means "the next class in the MRO of the instance's type", and that is the whole story.

An inconsistent order — class X(A, B) where B is a subclass of A — raises TypeError: Cannot create a consistent method resolution order, because A cannot both precede and follow B.

Cooperative __init__

With several bases each needing initialisation, every class must call super().__init__() and forward what it does not consume:

class Base:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)          # ends at object.__init__(), which takes nothing

class Timestamped(Base):
    def __init__(self, *, created, **kwargs):
        super().__init__(**kwargs)
        self.created = created

class Named(Base):
    def __init__(self, *, name, **kwargs):
        super().__init__(**kwargs)
        self.name = name

class Event(Timestamped, Named):
    pass

e = Event(created="2024-05-01", name="deploy")
Event.__mro__       # (Event, Timestamped, Named, Base, object)

Each __init__ takes its own arguments by keyword, passes the rest up, and sets its own attribute. The chain Event → Timestamped → Named → Base → object runs once through every class. Keyword-only parameters with **kwargs are what make this work regardless of the order the bases are combined in; positional arguments would have to agree on a position across unrelated classes.

Mixins

A mixin is a class that provides one capability, holds no state of its own (or very little), and is meant to be combined with a real base:

import json

class JsonMixin:
    def to_json(self):
        return json.dumps(self.__dict__, sort_keys=True)

class ReprMixin:
    def __repr__(self):
        fields = ", ".join(f"{k}={v!r}" for k, v in sorted(self.__dict__.items()))
        return f"{type(self).__name__}({fields})"

class Point(ReprMixin, JsonMixin):
    def __init__(self, x, y):
        self.x, self.y = x, y

Point(1, 2)              # Point(x=1, y=2)
Point(1, 2).to_json()    # '{"x": 1, "y": 2}'

Mixins go before the concrete base in the bases list so that their methods take precedence, are named …Mixin by convention, and depend on the host class only through a documented attribute or method (self.__dict__ here). This is the everyday use of multiple inheritance in Python: socketserver.ThreadingMixIn, Django's view mixins, unittest test mixins — small, orthogonal, composable.

Reading an unfamiliar hierarchy

When a method behaves unexpectedly in a class with several bases, three lines settle it: print(Cls.__mro__) for the order, Cls.method (without calling) to see which class the attribute resolves to — its __qualname__ names the defining class — and inspect.getsource when the body matters. Most surprises are a mixin listed after the base it was meant to override, or an __init__ in the chain that does not call super() and so silently ends the cooperative sequence for every class after it.

Interfaces through ABCs

Combining several ABCs is multiple inheritance too: class Deck(Sequence, Hashable) promises both protocols. Since ABCs carry little or no state and their abstract methods are disjoint, they combine without cooperative-__init__ concerns.

When to avoid it

  • Two concrete classes with real state and their own __init__ signatures. Their initialisers will not compose without the **kwargs discipline, and their attributes may collide. Compose instead: hold one as an attribute of the other.
  • Inheriting for code reuse when there is no is-a relationship. A Stack(list) is not a list; a Logger(Thread) is not a thread. Composition again.
  • Deep or wide hierarchies that make __mro__ a research task. If a reader must print the MRO to predict which method runs, the design has failed.

ClassName.__mro__ and ClassName.mro() show the order; inspect.getmro too. Print it when in doubt — but design so that nobody has to.

Pitfalls

  • Base.__init__(self) by name in a mixin, breaking the chain for other bases.
  • Positional arguments in cooperative __init__s.
  • A mixin placed after the base, so the base's method wins.
  • Expecting super() to mean "my parent" in a diamond.
  • Two bases defining the same attribute name for different purposes.
  • Multiple inheritance where an attribute holding the other object would be clearer.

Key takeaways

  • The MRO is a C3 linearisation: subclass first, bases left to right, a shared base after all its subclasses; Class.__mro__ shows it.
  • super() calls the next class in the instance's MRO, which in a diamond is a sibling, not the parent.
  • Cooperative __init__: every class calls super().__init__(**kwargs) and takes its own arguments by keyword.
  • Mixins add one capability, hold no state, go first in the bases list, and are the everyday use of multiple inheritance.
  • Combine ABCs freely; avoid combining stateful concrete classes — compose them instead.

Common questions

How does Python solve the diamond problem?

For class D(B, C) where B and C both inherit from A, the MRO is D, B, C, A, object: every class appears once and A comes after both of its subclasses. So on a D instance, super() inside B calls C, not A, and a cooperative chain runs A's method only once.

What does super() call in multiple inheritance?

super() calls the next class in the MRO of the instance's type, not necessarily the parent of the class where it is written. In a diamond that next class can be a sibling, which is what lets every class in the hierarchy run exactly once.

What is a mixin in Python?

A mixin is a small class that adds one capability, such as a to_json method or a generic __repr__, holds little or no state, and is meant to be combined with a real base class. It goes before the base in the bases list so its methods take precedence, and is named …Mixin by convention.

How do I write __init__ for multiple inheritance in Python?

Make it cooperative: every class takes its own arguments as keyword-only parameters, calls super().__init__(**kwargs) with the rest, then sets its own attributes. The chain then passes through every class once and ends at object.__init__, whatever order the bases are combined in.

What causes TypeError: Cannot create a consistent method resolution order?

Listing bases in an order C3 cannot satisfy, typically a base before its own subclass, as in class X(A, B) where B inherits from A. A would have to come both before and after B; list the subclass first.

Exercises

Trace the MRO

Define the diamond A, B(A), C(A), D(B, C), each with who() returning its own letter followed by > and super().who() (A returns just A). Read class names, one per line; for each print the class's __mro__ as the class names joined by spaces, then the result of who() on a fresh instance.

Input: lines, each A, B, C or D. Output: two lines per input line: mro: … and who: ….

D
B

prints

mro: D B C A object
who: D>B>C>A
mro: B A object
who: B>A

Cooperative initialisation

Write three mixins with cooperative initialisers — Named (keyword name), Timestamped (keyword created) and Tagged (keyword tags, a comma-separated string stored as a sorted list) — each taking its own argument by keyword, forwarding **kwargs with super().__init__(**kwargs), and Event(Tagged, Timestamped, Named). Read one line of key=value pairs, build Event(**pairs), print its attributes in alphabetical order, then its MRO.

Input: one line with name=…, created=…, tags=… in any order. Output: created …, name …, tags … (tags space-separated), then mro: ….

name=deploy tags=b,a created=2024-05-01

prints

created 2024-05-01
name deploy
tags a b
mro: Event Tagged Timestamped Named object

In this module: Inheritance, protocols and duck typing

← Abstract base classes — abc and collections.abc · Protocols and structural typing — typing.Protocol →