Python · lesson 16 of 34
Beginner
16- 1 Introduction to Python
- 2 Setting Up Python
- 3 Python Variables and Type System
- 4 Python Data Types
- 5 Python Operators
- 6 Python Strings
- 7 Control Flow in Python
- 8 Python Functions and Parameters
- 9 Python Data Structures
- 10 Python Collections
- 11 Python Modules and Packages
- 12 Object-Oriented Programming in Python
- 13 Encapsulation and Name Mangling in Python
- 14 Polymorphism and Duck Typing in Python
- 15 Abstraction with Abstract Base Classes in Python
- 21 Building a Python CLI Tool
Intermediate
12- 16 Multiple Inheritance and MRO in Python
- 17 Composition vs Inheritance in Python
- 18 Python Dataclasses
- 19 Error Handling in Python
- 20 File I/O, pathlib, and JSON in Python
- 22 Python Decorators and Closures
- 23 Python Generators
- 24 Python Context Managers
- 25 Python Concurrency
- 27 Testing Python Code
- 28 Python Security
- 34 Python Interview Prep
Advanced
6Multiple Inheritance and MRO in Python
See how Python resolves the diamond problem with C3 linearization (MRO), and use mixins to combine behavior from unrelated classes safely.
By SkillByExample · Updated
A class can inherit from more than one base class at once. When those bases share a common ancestor, Python needs a deterministic rule for which implementation wins — that rule is the Method Resolution Order (MRO). Output below is from Python 3.13.
The Diamond Problem, Resolved
D inherits from both B and C, which both inherit from A — the classic diamond shape.
class A:
def greet(self):
return "A"
class B(A):
def greet(self):
return "B -> " + super().greet()
class C(A):
def greet(self):
return "C -> " + super().greet()
class D(B, C): # the classic "diamond": D -> B -> C -> A
def greet(self):
return "D -> " + super().greet()
print([cls.__name__ for cls in D.__mro__])
print(D().greet())
$ python diamond.py
['D', 'B', 'C', 'A', 'object']
D -> B -> C -> A
Even though both B and C call super().greet(), A only appears once in the chain and its greet() only runs once. The C3 linearization algorithm flattens the diamond into a single order — D, B, C, A, object — and each class’s super() call moves to the next class in that order, not necessarily its own direct parent. B.greet’s super() lands on C, not A, because C comes next in D’s MRO.
Mixins: Multiple Inheritance Without a Diamond
The most common practical use of multiple inheritance isn’t a diamond at all — it’s combining small, focused mixin classes that each add one capability.
class JsonMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class LoggingMixin:
def log(self, message):
return f"[{type(self).__name__}] {message}"
class User(JsonMixin, LoggingMixin): # combine two independent, unrelated behaviors
def __init__(self, name, age):
self.name = name
self.age = age
u = User("Kavya", 29)
print(u.to_json())
print(u.log("created"))
print([cls.__name__ for cls in User.__mro__])
$ python mixins.py
{"name": "Kavya", "age": 29}
[User] created
['User', 'JsonMixin', 'LoggingMixin', 'object']
JsonMixin and LoggingMixin share no common ancestor besides object, so there’s no diamond to resolve — the MRO is simply User, then each mixin in the order listed. User picks up to_json() and log() without either mixin knowing the other exists, which is exactly the point of a mixin: bundle one reusable behavior, combine freely.
Practice
1. Add a class E(C, B), with the same two parents as D but in reverse order, and compare its MRO to D’s.
Solution
class A:
def greet(self):
return "A"
class B(A):
def greet(self):
return "B -> " + super().greet()
class C(A):
def greet(self):
return "C -> " + super().greet()
class D(B, C):
def greet(self):
return "D -> " + super().greet()
class E(C, B):
def greet(self):
return "E -> " + super().greet()
print([cls.__name__ for cls in D.__mro__])
print([cls.__name__ for cls in E.__mro__])
print(E().greet())
$ python reversed_bases.py
['D', 'B', 'C', 'A', 'object']
['E', 'C', 'B', 'A', 'object']
E -> C -> B -> A
Swapping the order of the base classes swaps B and C in the MRO, and with it the order the super() chain visits them. A still runs once, at the end.
2. Try declaring class F(A, B): where B already inherits from A. Run it and read the error Python raises.
Solution
class A:
pass
class B(A):
pass
try:
class F(A, B):
pass
except TypeError as e:
print(f"TypeError: {e}")
$ python bad_mro.py
TypeError: Cannot create a consistent method resolution order (MRO) for bases A, B
C3 linearization requires a subclass to come before its bases. Listing A before B asks for A to be searched first, but B inherits from A and must come before it. No order satisfies both, so Python refuses to create the class.
3. Add a third mixin, ComparableMixin, that implements __eq__ by comparing __dict__, and combine it with User.
Solution
import json
class JsonMixin:
def to_json(self):
return json.dumps(self.__dict__)
class ComparableMixin:
def __eq__(self, other):
return isinstance(other, type(self)) and self.__dict__ == other.__dict__
class User(JsonMixin, ComparableMixin):
def __init__(self, name, age):
self.name = name
self.age = age
print(User("Kavya", 29) == User("Kavya", 29))
print(User("Kavya", 29) == User("Kavya", 30))
print(User("Kavya", 29).to_json())
$ python comparable_mixin.py
True
False
{"name": "Kavya", "age": 29}
Without the mixin, == falls back to object.__eq__, which compares identity and returns False for two separately built users. The mixin swaps in value equality without touching User.
4. Use D.mro() (the method form) instead of D.__mro__ (the attribute) and compare what they return.
Solution
class A:
def greet(self):
return "A"
class B(A):
def greet(self):
return "B -> " + super().greet()
class C(A):
def greet(self):
return "C -> " + super().greet()
class D(B, C):
pass
print(type(D.__mro__).__name__, type(D.mro()).__name__)
print(list(D.__mro__) == D.mro())
$ python mro_method.py
tuple list
True
mro() returns a list; __mro__ is a tuple. The order is identical. mro() exists as a method so a metaclass can override it to customize the order.