Python multiple inheritance is the ability for a class to inherit from more than one parent class simultaneously, combining the attributes and methods of all its parents into a single child class. While single inheritance, covered in the previous article, creates a straightforward parent-child chain with no ambiguity about where methods come from, multiple inheritance introduces the question of what happens when two parents define a method with the same name. Python answers this question with a deterministic algorithm called the method resolution order, or MRO, which defines the exact sequence in which parent classes are searched when a method is called. Understanding the MRO is the key to using multiple inheritance effectively, and misunderstanding it is the source of most multiple inheritance bugs.
The article on single inheritance in Python covered the fundamentals of extending one parent class. Multiple inheritance extends that pattern by allowing a comma-separated list of parent classes in the class definition. The child receives everything from every parent, and when a method name exists in more than one parent, the MRO determines which version runs. This article explains how the MRO works, how to design classes that cooperate correctly in a multiple inheritance hierarchy using the super() function, and when multiple inheritance is and is not the right design choice.
Multiple inheritance is a feature that divides the Python community. Some developers use it extensively, particularly through mixins, which are small classes designed to add a specific piece of functionality to any class that inherits from them. Other developers avoid it entirely, preferring composition or single inheritance for all code reuse. Both positions have merit, and the right choice depends on the specific design problem you are solving. What is not optional is understanding how the MRO works, because even if you never write a class with multiple parents, the Python standard library and many third-party packages use multiple inheritance, and you will encounter it when reading and debugging code.
The method resolution order
When Python needs to find a method on an object, it searches the object's class first, then its parent classes in a specific order, then its grandparent classes, and so on up the inheritance chain. With single inheritance, the search order is obvious: the class, then the parent, then the grandparent, straight up the chain. With multiple inheritance, the search order depends on the order in which parent classes are listed and on the inheritance relationships among those parents. Python computes this order once when the class is defined, and you can inspect it at any time using the mro() method on the class.
The algorithm Python uses is called C3 linearization, and it guarantees two important properties. First, a class always appears before its parents in the MRO, so methods defined on a child class take priority over methods with the same name on any parent. Second, the order of parent classes as listed in the class definition is preserved, so if you write class Child(A, B), methods from A are checked before methods from B when both define the same name. The C3 algorithm also ensures that the MRO is consistent across the entire inheritance graph, which prevents the diamond problem where a grandparent class might be visited multiple times.
You do not need to memorize the C3 algorithm to use multiple inheritance effectively. You do need to know that you can inspect the MRO at any time, and you should inspect it whenever a method call does not resolve to the class you expected. Every Python class has an mro() method that returns a list of classes in resolution order. If you are unsure which version of a method will be called, checking the MRO answers the question definitively.
Cooperative multiple inheritance with super()
The super() function behaves differently in a multiple inheritance context than it does in single inheritance. In single inheritance, super() simply delegates to the parent class. In multiple inheritance, super() delegates to the next class in the MRO, which may not be the direct parent of the class where the call appears. This behavior is the foundation of cooperative multiple inheritance, where every class in the hierarchy calls super() to pass control along the MRO chain, ensuring that every class's method runs exactly once in the correct order.
Here is a cooperative multiple inheritance example where three classes each contribute to a common task through their constructors:
class Base:
def __init__(self):
self.base_value = 0
class Adder(Base):
def __init__(self):
super().__init__()
self.adder_value = 1
class Multiplier(Base):
def __init__(self):
super().__init__()
self.multiplier_value = 2The Base class defines a simple attribute. The Adder and Multiplier classes each extend the constructor, calling super().init() to continue the MRO chain. Now a class that inherits from both can combine their capabilities:
class Calculator(Adder, Multiplier):
def __init__(self):
super().__init__()
self.result = self.base_value + self.adder_value * self.multiplier_valueThe Calculator class lists Adder first and Multiplier second. The MRO is Calculator, Adder, Multiplier, Base, object. When super().init() is called inside Calculator, it delegates to Adder, which calls super().init() and delegates to Multiplier, which calls super().init() and delegates to Base. Every constructor in the chain runs exactly once, and the Calculator object ends up with all three attributes initialized correctly. This cooperative pattern depends on every class in the hierarchy calling super() consistently. If one class calls a specific parent by name instead of using super(), the chain breaks and some classes in the MRO are skipped.
Mixins: the most practical use of multiple inheritance
A mixin is a class designed to add a specific, focused piece of functionality to any class that inherits from it. Mixins are typically small, with a single responsibility, and they are not meant to be instantiated on their own. They exist purely to be combined with other classes through multiple inheritance. A LoggingMixin might add logging to any class. A SerializationMixin might add JSON serialization. An AuthenticationMixin might add login and logout methods. The class that inherits from a mixin receives the mixin's methods as if they were defined directly on the class.
The naming convention for mixins is to end the class name with Mixin, which signals to readers that the class is designed for multiple inheritance and is not a standalone entity. When a class inherits from both a primary base class and one or more mixins, the base class is typically listed last, and the mixins are listed first. This ordering ensures that methods from the base class take priority over methods from the mixins in the MRO, which is usually the desired behavior because the base class defines what the object fundamentally is.
Mixins are the most widely accepted use of multiple inheritance in the Python community because they avoid the complexity of inheriting from multiple full-featured classes. Each mixin does one thing, the interactions between mixins are minimal, and the primary base class provides the core identity. If you find yourself using multiple inheritance, the mixin pattern is the safest way to do it.
When to avoid multiple inheritance
Multiple inheritance should be avoided when the parent classes are not designed to work together, when they have conflicting method names that cannot be resolved cleanly through the MRO, or when the inheritance graph becomes deep and tangled. A common warning sign is a class that inherits from multiple full-featured parent classes, each with its own constructor that takes different arguments and its own set of methods that overlap in unpredictable ways. In these situations, composition, where a class holds instances of other classes as attributes rather than inheriting from them, is almost always the better design.
The article on inheritance in Python explained covers the is-a versus has-a distinction, and that distinction is even more important when multiple parents are involved. A class that inherits from both a DatabaseConnection and an EmailSender is likely a design error because the class uses a database connection and an email sender but is not fundamentally either one. Holding a DatabaseConnection and an EmailSender as attributes, and delegating work to them through methods, achieves the same functionality with clearer code and fewer surprises.
Rune AI
Key Insights
- Multiple inheritance lets a class inherit from more than one parent, combining attributes and methods from all of them.
- Python's method resolution order determines which parent's method is called; you can inspect it with the mro() method or mro attribute.
- The C3 linearization algorithm ensures a consistent MRO where each class appears once and parents follow children.
- Cooperative multiple inheritance requires all parent classes to use super() consistently so the MRO chain works correctly.
- Use multiple inheritance deliberately and sparingly; composition is often a simpler and more maintainable alternative.
Frequently Asked Questions
Does Python support inheriting from multiple parent classes?
What is the diamond problem and how does Python solve it?
When should I avoid multiple inheritance?
Conclusion
Multiple inheritance is one of Python's most powerful and most easily misused features. When applied with care to well-designed class hierarchies, it enables elegant solutions like mixins and cooperative inheritance. When applied without understanding the method resolution order and the cooperative super() pattern, it produces confusing bugs. The key is to understand the MRO, to design parent classes that work together through super(), and to prefer composition when the inheritance graph starts to look complicated.
More in this topic
Create Custom Iterators in Python
Learn how to build your own iterators in Python by implementing the __iter__() and __next__() methods, with practical examples and reusable patterns.
Python Iterator Protocol Explained
Learn how Python's iterator protocol works with the __iter__() and __next__() methods, and understand the contract every iterator must follow.
Python Iterables and Iterators Explained
Learn what iterables and iterators are in Python, how they power for loops under the hood, and why understanding them unlocks cleaner data processing patterns.