Skip to main content
Python · lesson 16 of 34
Python intermediate Lesson 16 of 34

Multiple 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.

Frequently Asked Questions

Does Python support multiple inheritance?
Yes — a class can list more than one base class, e.g. class D(B, C). Python resolves method calls across all of them using the Method Resolution Order (MRO), computed with the C3 linearization algorithm.
What is the diamond problem?
It occurs when class D inherits from both B and C, and B and C both inherit from a common class A. Without a defined resolution order, it's ambiguous which path to A a call from D should follow. Python's MRO resolves this to one deterministic, linear order.
How do I check a class's method resolution order?
Access ClassName.__mro__ (a tuple) or call ClassName.mro() (a list). Both list the classes in the exact order Python searches them for attributes and methods.
What is a mixin?
A mixin is a class designed to be combined with others via multiple inheritance to add one specific piece of reusable behavior (like JSON serialization or logging), without being meant to stand on its own as a complete class.