Python Multi-Inheritance
Object-Oriented Python Object-Oriented Python

Python Multiple Inheritance and MRO

1Python Multi-Inheritance: overview

Multiple inheritance means a class lists more than one parent, such as class Duck(Swimmer, Flyer); Python then follows one fixed search order, the Method Resolution Order (MRO), to decide which parent's method or attribute is used.

GoalBy the end you can write a class with several parents, read its MRO with mro, explain how Python resolves the diamond problem, chain super() calls so every class runs exactly once, forward **kwargs through init and build small mixin classes.

Before this lesson: Python Inheritance, Python Polymorphism and Duck Typing

2Python Multi-Inheritance: diagram

Python Multiple Inheritance and MRODuck(Swimmer, Flyer)the class itself is always searched firstthenSwimmer(Animal)left parent: listed first, so searched firstthenFlyer(Animal)right parent: searched after the left onethenAnimalshared parent: waits until BOTH children are donethenobjectthe root of every class: always last
This is the MRO of Duck(Swimmer, Flyer): one straight line with no repeats. A class comes before its parents, parents keep the order you wrote, and the shared parent Animal waits until both Swimmer and Flyer have had their turn.

3Python Multi-Inheritance: explained step by step

In Python Inheritance every child class had one parent. Python also lets a class list several parents at once. That is handy when a new kind of thing genuinely combines abilities from different places: a smartphone is both a phone and a camera.

Listing more than one parent

Put every parent in the parentheses, separated by commas: class Smartphone(Phone, Camera):. The new class receives the methods and attributes of all its parents, so nothing has to be copied.

Which parent wins?

If two parents define a method with the same name, Python has to pick one. The rule is simple: it checks the child first, then the parents from left to right, and stops at the first class that has the name.

Python
class Camera:
    def take_photo(self):
        return "click!"

    def describe(self):
        return "a camera"

class Phone:
    def call(self, number):
        return f"calling {number}"

    def describe(self):
        return "a phone"

class Smartphone(Phone, Camera):
    pass

class PhotoPhone(Camera, Phone):
    pass

for device in (Smartphone(), PhotoPhone()):
    print(type(device).__name__, "->", device.describe(), "|", device.take_photo(), "|", device.call("555-0101"))
print([cls.__name__ for cls in Smartphone.__mro__])
print([cls.__name__ for cls in PhotoPhone.mro()])

Both classes can do everything, but describe() follows the order in the parentheses. The only difference between Smartphone and PhotoPhone is which parent is written first.

The Method Resolution Order (MRO)

Behind the scenes every class keeps the exact list of classes Python searches. It is called the method resolution order, or MRO. Smartphone.__mro__ gives it as a tuple and Smartphone.mro() gives it as a list. Whenever you call a method or read an attribute, Python walks that list from the left and uses the first class that has the name. object is always last, because every class inherits from it.

Printing the classes themselves is noisy, so print their names: [cls.__name__ for cls in Smartphone.__mro__].

The diamond problem

Things get interesting when two parents share a parent of their own. Draw the classes and you get a diamond:

        Animal
        /    \
   Swimmer  Flyer
        \    /
         Duck

Where should Animal go in the search? If Python looked in Animal before Flyer, the general Animal version of a method would hide the more specific Flyer version. Python avoids this with a rule called C3 linearisation. You do not need the algorithm, only the three promises it keeps:

  1. A class always comes before its own parents.
  2. Parents keep the left-to-right order you wrote them in.
  3. Every class appears only once, so a shared parent such as Animal must wait until every class that inherits from it has had its turn.

For Duck(Swimmer, Flyer) that produces Duck, Swimmer, Flyer, Animal, object: one straight line with no repeats.

Python
class Animal:
    def move(self):
        return "walks"

class Swimmer(Animal):
    def move(self):
        return "swims"

class Flyer(Animal):
    def move(self):
        return "flies"

class Duck(Swimmer, Flyer):
    pass

print([cls.__name__ for cls in Duck.__mro__])
print("A duck", Duck().move())
print("A plain flyer", Flyer().move())

super() follows the MRO, not "the parent"

So far super() has meant "my parent class". With several parents that is not quite true. super() really means "the next class after me in the MRO of the object I am working on". Inside a Duck, the class after Swimmer is Flyer, not Animal.

If every class in the diamond calls super(), the call travels down the whole MRO and each class runs exactly once. This teamwork is called cooperative inheritance:

Python
class Animal:
    def describe(self):
        return ["an animal"]

class Swimmer(Animal):
    def describe(self):
        return ["can swim"] + super().describe()

class Flyer(Animal):
    def describe(self):
        return ["can fly"] + super().describe()

class Duck(Swimmer, Flyer):
    def describe(self):
        return ["a duck"] + super().describe()

print(", ".join(Duck().describe()))
print(", ".join(Swimmer().describe()))

Notice that Swimmer().describe() skips Flyer. A plain Swimmer has no Flyer in its MRO, so the very same Swimmer code reaches Animal there. Same code, different object, different "next class".

Cooperative __init__ and **kwargs

Because you cannot know which class comes next, you cannot hand positional arguments to "the" parent. The fix is for every __init__ to pick out the arguments it needs by name and forward the rest with **kwargs to whichever class is next:

Python
class Animal:
    def __init__(self, name, **kwargs):
        super().__init__(**kwargs)
        self.name = name

class Swimmer(Animal):
    def __init__(self, swim_speed, **kwargs):
        super().__init__(**kwargs)
        self.swim_speed = swim_speed

class Flyer(Animal):
    def __init__(self, wingspan, **kwargs):
        super().__init__(**kwargs)
        self.wingspan = wingspan

class Duck(Swimmer, Flyer):
    pass

duck = Duck(name="Daisy", swim_speed=5, wingspan=90)
print(duck.name, duck.swim_speed, duck.wingspan)

Each class used its own keyword and passed the leftovers on. At the end of the line, Animal hands an empty **kwargs to object. A misspelled keyword would travel all the way there and raise a TypeError, which is exactly the alarm you want.

Mixins: small classes that add one skill

A mixin is a small class designed to be mixed into other classes. It does one job (turn the object into JSON, write log lines, add a timestamp), usually has no __init__ and is never meant to be used on its own. By convention its name ends in Mixin. List the mixins first and the "real" base class last, so a mixin's version of a method is found before the base class's version:

Python
import json

class Document:
    def __init__(self, title, body):
        self.title = title
        self.body = body

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

class LoggingMixin:
    def log(self, message):
        print(f"[{type(self).__name__}] {message}")

class Report(JsonMixin, LoggingMixin, Document):
    pass

report = Report("Q3 sales", "Revenue grew.")
report.log("saved")
print(report.to_json())
print([cls.__name__ for cls in Report.__mro__])

Report gained two skills without writing any code of its own, and Document stayed a plain class that knows nothing about JSON or logging.

When Python says no

If the order you write the parents in breaks the three promises, Python refuses when the class is created instead of guessing. Here Broken lists a parent before its own child, which cannot be satisfied:

Python
class Base:
    pass

class Child(Base):
    pass

try:
    class Broken(Base, Child):
        pass
except TypeError as err:
    print("TypeError:", err)

Use it with care

Multiple inheritance is a sharp tool. A reader has to keep the whole MRO in their head, and a change in one parent can quietly change which method runs. A few habits keep it safe:

  • Prefer one real base class plus a few small, single-purpose mixins.
  • Avoid giving different parents methods with the same name unless you mean to override them.
  • If you need to print __mro__ just to understand your own code, simplify it.
  • When a class merely uses another object, do not inherit at all. Composition (a car has an engine) is usually simpler, and it is the topic of the next lesson.

4Python Multi-Inheritance: syntax

class Child(ParentA, ParentB):          # search order: Child, ParentA, ParentB, ..., object
    pass

Child.__mro__                           # tuple of classes, in search order
Child.mro()                             # the same order, as a list
[c.__name__ for c in Child.__mro__]     # readable names

class Base:
    def __init__(self, name, **kwargs):
        super().__init__(**kwargs)      # cooperative: pass the rest to the next class in the MRO
        self.name = name

class ExtraMixin:                       # small single-purpose helper
    def extra(self):
        return "..."

class Concrete(ExtraMixin, Base):       # mixins first, the real base class last
    pass

5Python Multi-Inheritance: run the example

Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.

Interactive example
class Animal:
    def __init__(self):
        print("Animal ready")

class Swimmer(Animal):
    def __init__(self):
        print("Swimmer ready")
        super().__init__()

class Flyer(Animal):
    def __init__(self):
        print("Flyer ready")
        super().__init__()

class Duck(Swimmer, Flyer):
    def __init__(self):
        print("Duck ready")
        super().__init__()

duck = Duck()
print([cls.__name__ for cls in Duck.__mro__])
Expected outputDuck ready
Swimmer ready
Flyer ready
Animal ready
['Duck', 'Swimmer', 'Flyer', 'Animal', 'object']

7Python Multi-Inheritance: how the example works

Hover or tap a step to highlight its lines in the example above.

  1. Line 1-3Animal sits at the top of the diamond. Its __init__ only announces itself. It does not call super() because object has nothing to add.
  2. Line 5-8Swimmer is a child of Animal. It prints a message and then calls super().__init__(). Remember that super() means "the next class in the MRO", which depends on the object being built.
  3. Line 10-13Flyer is written the same way. It is a second, separate child of Animal.
  4. Line 15-18Duck(Swimmer, Flyer) has two parents that share Animal, which makes a diamond. Its own __init__ prints and then calls super().__init__() as well.
  5. Line 20Creating one Duck starts the chain: Duck, then Swimmer, then Flyer, then Animal. Animal runs only once even though both parents inherit from it, and Swimmer's super() call lands on Flyer, not on Animal.
  6. Line 21Duck.__mro__ is the plan Python followed. The __init__ chain visited the classes in exactly this order, and object is always at the end.

8Python Multi-Inheritance: common mistakes

Listing a parent before its own child

✗ Wrong

class Vehicle:
    pass

class Car(Vehicle):
    pass

class Taxi(Vehicle, Car):
    pass
TypeError: Cannot create a consistent method resolution order (MRO) for bases Vehicle, Car

✓ Right

class Vehicle:
    pass

class Car(Vehicle):
    pass

class Taxi(Car, Vehicle):
    pass

print([cls.__name__ for cls in Taxi.__mro__])

Python must put Car before its parent Vehicle, but your order asks for Vehicle first, so no valid MRO exists. List the more specific class first, or simply inherit from Car alone since it already brings Vehicle along.

Calling parents directly instead of using super()

✗ Wrong

class Animal:
    def __init__(self):
        print("Animal ready")

class Swimmer(Animal):
    def __init__(self):
        Animal.__init__(self)
        print("Swimmer ready")

class Flyer(Animal):
    def __init__(self):
        Animal.__init__(self)
        print("Flyer ready")

class Duck(Swimmer, Flyer):
    def __init__(self):
        Swimmer.__init__(self)
        Flyer.__init__(self)

Duck()
No error, but the output is: Animal ready, Swimmer ready, Animal ready, Flyer ready. The shared base ran twice.

✓ Right

class Animal:
    def __init__(self):
        print("Animal ready")

class Swimmer(Animal):
    def __init__(self):
        print("Swimmer ready")
        super().__init__()

class Flyer(Animal):
    def __init__(self):
        print("Flyer ready")
        super().__init__()

class Duck(Swimmer, Flyer):
    pass

Duck()

Naming a parent explicitly runs it every time, so a shared parent runs once per path. Call super().__init__() in every class instead and let the MRO visit each class exactly once: now Animal ready appears a single time.

Passing positional arguments along a diamond

✗ Wrong

class Animal:
    def __init__(self, name):
        self.name = name

class Swimmer(Animal):
    def __init__(self, name, speed):
        super().__init__(name)
        self.speed = speed

class Flyer(Animal):
    def __init__(self, name, span):
        super().__init__(name)
        self.span = span

class Duck(Swimmer, Flyer):
    pass

duck = Duck("Daisy", 5)
TypeError: Flyer.__init__() missing 1 required positional argument: 'span'

✓ Right

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

class Swimmer(Animal):
    def __init__(self, speed, **kwargs):
        super().__init__(**kwargs)
        self.speed = speed

class Flyer(Animal):
    def __init__(self, span, **kwargs):
        super().__init__(**kwargs)
        self.span = span

class Duck(Swimmer, Flyer):
    pass

duck = Duck(name="Daisy", speed=5, span=90)
print(duck.name, duck.speed, duck.span)

You expected Swimmer's super() to reach Animal, but inside a Duck the next class in the MRO is Flyer. Take your own arguments by keyword and forward everything else with **kwargs.

Putting a mixin after the class it should change

✗ Wrong

class Greeter:
    def greet(self):
        return "Hello"

class ShoutMixin:
    def greet(self):
        return super().greet().upper() + "!"

class LoudGreeter(Greeter, ShoutMixin):
    pass

print(LoudGreeter().greet())
Prints Hello. There is no error, but the mixin is silently ignored.

✓ Right

class Greeter:
    def greet(self):
        return "Hello"

class ShoutMixin:
    def greet(self):
        return super().greet().upper() + "!"

class LoudGreeter(ShoutMixin, Greeter):
    pass

print(LoudGreeter().greet())

The MRO is LoudGreeter, Greeter, ShoutMixin, so Greeter.greet is found first and the mixin's version is never reached. Write the mixin before the base class so its method comes first.

9Python Multi-Inheritance: exercises

Write your answer, then press Check. The checks run in your browser.

1. Combine two parents

Write code Easy

Clock and StepCounter both have a describe() method. Create a class FitnessWatch that inherits from both so that it can call time() and steps(), and so that describe() returns the step counter's text, "a step counter". Do not change the two given classes and do not write any methods in FitnessWatch.

Your code
class Clock:
    def time(self):
        return "09:30"

    def describe(self):
        return "a clock"

class StepCounter:
    def steps(self):
        return 4200

    def describe(self):
        return "a step counter"

# Create the class FitnessWatch below.

2. Predict the cooperative chain

Predict the output Medium

Every roles() method calls super().roles(). Work out the MRO of Mentor and type exactly what the program prints.

Read this code
class Person:
    def roles(self):
        return ["person"]

class Teacher(Person):
    def roles(self):
        return ["teacher"] + super().roles()

class Coach(Person):
    def roles(self):
        return ["coach"] + super().roles()

class Mentor(Teacher, Coach):
    def roles(self):
        return ["mentor"] + super().roles()

print(Mentor().roles())
print(Teacher().roles())
print([cls.__name__ for cls in Coach.__mro__])

2 more exercises → 5 more examples

Finished this lesson?
Progress is saved in this browser — create a free account to keep it across devices.

Questions learners ask

What is the MRO in Python and how do I see it?

The MRO (method resolution order) is the list of classes Python searches, from left to right, when you use a method or attribute on an object. See it with ClassName.mro (a tuple) or ClassName.mro() (a list). The first class in the list that has the name wins.

Does super() call the parent class?

Not exactly. super() calls the next class in the MRO of the object you are working on. With single inheritance that is the parent, but in a diamond it can be a sibling: inside a Duck, Swimmer's super() reaches Flyer. That is what lets each class in a diamond run once.

What is a mixin and how is it different from a normal base class?

A mixin is a small class that adds one skill (such as to_json or logging) and is not meant to be used alone. A normal base class describes what something is; a mixin only adds a capability. Put mixins before the base class in the parentheses so their methods are found first.

Further reading

10Next lesson

Up nextPython Composition: Has-A vs Is-AComposition means building a class out of other objects it holds as attributes (a Car has an Engine) and delegating wor…

Related:
Python InheritancePython Polymorphism and Duck TypingPython Composition: Has-A vs Is-A

← Python Polymorphism and Duck Typing · All Object-Oriented Python lessons · Practise in the IDE