Multiple Inheritance & MRO
Advanced · 18 min read · ▶ live playground · ✦ checkpoint
A class can inherit from two parents at once. That sounds like a shortcut, and sometimes it is. Multiple inheritance is a subclass having more than one parent class, and Python makes it work by keeping one precise method-search list.
Inherit from two parents
You write multiple parent classes inside the parentheses, separated by commas. Keep the house-blueprint picture from the inheritance lesson: this is like starting a new blueprint from two small add-on sheets. One sheet adds name-card behavior. Another adds score-card behavior.
class NameTag:
def name_line(self):
return f"Player: {self.name}"
class ScoreTag:
def score_line(self):
return f"Score: {self.score}"
class PlayerCard(NameTag, ScoreTag):
def __init__(self, name, score):
self.name = name
self.score = score
def print_card(self):
print(self.name_line())
print(self.score_line())
card = PlayerCard("Mina", 140)
card.print_card()Player: MinaScore: 140
PlayerCard inherits name_line from NameTag and score_line from ScoreTag. The child class owns the actual player state: self.name and self.score.
This works because the two parent classes add different method names. Nothing is competing yet. Multiple inheritance gets interesting when two parents offer the same method name.
Read the MRO
The Method Resolution Order, or MRO, is the ordered list of classes Python searches when it needs a method or attribute. First Python checks the class of the object. Then it walks through the MRO until it finds the name.
Think of the MRO as a seating chart for method lookup. Python does not wander through the family tree. It makes one line, then checks seats from front to back.
class FriendlyLabel:
def label(self):
return f"{self.name} is ready"
class ScoreLabel:
def label(self):
return f"{self.name}: {self.score} points"
class FriendlyCard(FriendlyLabel, ScoreLabel):
def __init__(self, name, score):
self.name = name
self.score = score
class ScoreCard(ScoreLabel, FriendlyLabel):
def __init__(self, name, score):
self.name = name
self.score = score
def show_mro(cls):
names = [parent.__name__ for parent in cls.__mro__]
print(" -> ".join(names))
friendly = FriendlyCard("Mina", 140)
score = ScoreCard("Ravi", 200)
print(friendly.label())
print(score.label())
show_mro(FriendlyCard)
show_mro(ScoreCard)Mina is readyRavi: 200 pointsFriendlyCard -> FriendlyLabel -> ScoreLabel -> objectScoreCard -> ScoreLabel -> FriendlyLabel -> object
Both parent classes define label. FriendlyCard lists FriendlyLabel first, so that method wins. ScoreCard lists ScoreLabel first, so the score version wins.
__mro__ is the class attribute that stores this lookup order. You do not usually print it in app code, but it is perfect when you are learning or debugging class relationships.
Try it - change the parent order
Run this, then swap the parent order in PlayerBadge. The object data stays the same, but badge.label() changes because the MRO changes.
Why C3 exists
A diamond-shaped class tree is when two parent paths meet the same grandparent. Python must avoid running in circles or picking the shared grandparent twice.
class BaseLog:
def stamp(self):
return "base log"
class PlayerLog(BaseLog):
def stamp(self):
return "player log"
class ScoreLog(BaseLog):
def stamp(self):
return "score log"
class GameLog(PlayerLog, ScoreLog):
pass
names = [cls.__name__ for cls in GameLog.__mro__]
print(" -> ".join(names))
print(GameLog().stamp())GameLog -> PlayerLog -> ScoreLog -> BaseLog -> objectplayer log
BaseLog appears once, near the end. PlayerLog stays before ScoreLog because GameLog(PlayerLog, ScoreLog) asked for that left-to-right order.
C3 also refuses impossible orders. In this example, one parent path says First must come before Second, while the other says Second must come before First. Python will not guess.
class Base:
pass
class First(Base):
pass
class Second(Base):
pass
class FirstThenSecond(First, Second):
pass
class SecondThenFirst(Second, First):
pass
try:
class Impossible(FirstThenSecond, SecondThenFirst):
pass
except TypeError as error:
print(f"Rejected: {error}")Rejected: Cannot create a consistent method resolution order (MRO) for bases First, Second
That error is a design signal. The parent classes disagree about the only safe lookup order. The fix is not to fight the MRO. The fix is to simplify the class design.
Use mixins for small abilities
A mixin is a small behavior-only parent class meant to add one focused ability to another class. A mixin should not be the main identity of the object. It is a clip-on tool for the class blueprint.
Good mixins are boring:
- They add a small method, like
to_jsonorbadge. - They usually do not define
__init__. - They rely on the real class to provide the data or helper method they need.
import json
class JsonMixin:
def to_json(self):
return json.dumps(self.as_dict(), sort_keys=True)
class BadgeMixin:
def badge(self):
return f"{self.name} ({self.score} pts)"
class PlayerProfile(JsonMixin, BadgeMixin):
def __init__(self, name, score, mood):
self.name = name
self.score = score
self.mood = mood
def as_dict(self):
return {
"name": self.name,
"score": self.score,
"mood": self.mood,
}
profile = PlayerProfile("Mina", 180, "focused")
print(profile.badge())
print(profile.to_json())Mina (180 pts){"mood": "focused", "name": "Mina", "score": 180}
PlayerProfile is the real class. JsonMixin and BadgeMixin add abilities. JsonMixin expects as_dict to exist, and PlayerProfile provides it.
Use mixins when you can say "this class can also do this small thing." Avoid mixins when you are trying to model the main "is a" relationship. PlayerProfile is not a kind of JSON. It just knows how to turn itself into JSON text.
Checkpoint
Answer all three to mark this lesson complete
Multiple inheritance is powerful because Python gives it a real lookup order, not a shrug. Next, you meet the special method names that let your own objects act naturally with operators, containers, calls, and cleanup blocks.