Multiple Inheritance in Python

Learn how Python multiple inheritance works, how the method resolution order determines which parent method is called, and when to use multiple parents safely.

7 min read

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:

pythonpython
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 = 2

The 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:

pythonpython
class Calculator(Adder, Multiplier):
    def __init__(self):
        super().__init__()
        self.result = self.base_value + self.adder_value * self.multiplier_value

The 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Does Python support inheriting from multiple parent classes?

Yes, Python fully supports multiple inheritance. A class can list more than one parent class in its definition, and it will inherit attributes and methods from all of them. Python uses a method resolution order, commonly called the MRO, to determine which parent's method to call when multiple parents define a method with the same name. The MRO follows a predictable algorithm called C3 linearization that ensures consistent method lookup across the entire inheritance graph.

What is the diamond problem and how does Python solve it?

The diamond problem occurs when a class inherits from two parents that both inherit from a common grandparent. Without careful handling, the grandparent's methods might be called multiple times or in an unpredictable order. Python solves this with the C3 linearization algorithm, which produces a consistent method resolution order where each class appears exactly once and parent classes always appear after their children. The super() function respects this order, ensuring that each method in the chain is called exactly once.

When should I avoid multiple inheritance?

Avoid multiple inheritance when the class hierarchy is not carefully designed for it, when the parent classes have conflicting method names or incompatible initialization signatures, or when a simpler design using composition would achieve the same goal. Multiple inheritance is a powerful tool but it adds complexity. If you find yourself debugging MRO issues or puzzling over which parent's method is being called, consider whether the same functionality could be achieved with composition or with a flatter class hierarchy.

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.