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.
Before this lesson: Python Inheritance, Python Polymorphism and Duck Typing
2Python Multi-Inheritance: diagram
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.
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:
- A class always comes before its own parents.
- Parents keep the left-to-right order you wrote them in.
- Every class appears only once, so a shared parent such as
Animalmust 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.
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:
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:
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:
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:
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.
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.
- Line 1-3
Animalsits at the top of the diamond. Its__init__only announces itself. It does not callsuper()becauseobjecthas nothing to add. - Line 5-8
Swimmeris a child ofAnimal. It prints a message and then callssuper().__init__(). Remember thatsuper()means "the next class in the MRO", which depends on the object being built. - Line 10-13
Flyeris written the same way. It is a second, separate child ofAnimal. - Line 15-18
Duck(Swimmer, Flyer)has two parents that shareAnimal, which makes a diamond. Its own__init__prints and then callssuper().__init__()as well. - Line 20Creating one
Duckstarts the chain: Duck, then Swimmer, then Flyer, then Animal.Animalruns only once even though both parents inherit from it, andSwimmer'ssuper()call lands onFlyer, not onAnimal. - Line 21
Duck.__mro__is the plan Python followed. The__init__chain visited the classes in exactly this order, andobjectis 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✓ 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()✓ 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)✓ 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())✓ 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 ✓ CompletedClock 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.
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.describe()?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"
class FitnessWatch(StepCounter, Clock):
pass
watch = FitnessWatch()
print(watch.time(), watch.steps(), watch.describe())
2. Predict the cooperative chain
Predict the output Medium ✓ CompletedEvery roles() method calls super().roles(). Work out the MRO of Mentor and type exactly what the program prints.
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__])['mentor', 'teacher', 'coach', 'person']
['teacher', 'person']
['Coach', 'Person', 'object']
2 more exercises → 5 more examples
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