Multiple Inheritance & The Diamond Problem

RoadmapsPython

Overview

Multiple inheritance allows a subclass to inherit from more than one parent class. Python resolves method calls using the Method Resolution Order (`MRO`), which follows the C3 linearization algorithm. The "diamond problem" occurs when a subclass inherits from two classes that share a common ancestor. `MRO` ensures the common ancestor is visited exactly once, and only after all its descendants have been checked. Think of `MRO` as a priority queue: Python builds an ordered list of parent classes from left to right, depth-first, while guaranteeing that a parent is never checked before its children. class X: pass class Y(X): pass class Z(X, Y): pass # TypeError: Cannot create a consistent method resolution order (MRO) Python will raise a `TypeError` at class definition time if you create an impossible inheritance graph. The C3 linearization algorithm enforces that a base class like `X` must appear after its child `Y` in the `MRO`, which conflicts with the left-to-right order specified by `(X, Y)`.

It enables composition-like patterns via "Mixins". This allows classes to combine independent sets of behaviors without deep, rigid single-inheritance hierarchies. Common behaviors extracted into mixins include: - Logging: Adding standardized output tracing - Serialization: Converting object state to JSON or XML - Authentication: Enforcing access control before methods execute

Where used: Django `Mixin` classes, FastAPI dependency structures, GUI frameworks like PyQt

Why learn this

Code walkthrough

class Handler:
    def handle(self):
        print('Core logic')

class AuthMixin(Handler):
    def handle(self):
        print('Auth check')
        super().handle()

class CacheMixin(Handler):
    def handle(self):
        print('Cache check')
        super().handle()

class Endpoint(AuthMixin, CacheMixin):
    def handle(self):
        print('Endpoint hit')
        super().handle()

Endpoint().handle()

Focus: class Endpoint(AuthMixin, CacheMixin):

Aha moment

class Request:
    def process(self):
        print('Base Request')

class ValidateMixin(Request):
    def process(self):
        print('Validating')
        super().process()

class LogMixin(Request):
    def process(self):
        print('Logging')
        super().process()

class SecureRequest(ValidateMixin, LogMixin):
    pass

SecureRequest().process()

Prediction: What will SecureRequest().process() print?

Common guess: Validating Base Request

Because `ValidateMixin` inherits from `Request`, beginners assume `super()` inside `ValidateMixin` goes straight to `Request`. However, `super()` follows the `MRO` of the calling object, which is `SecureRequest`. The `MRO` is `[SecureRequest, ValidateMixin, LogMixin, Request]`. Because of this order, `super()` inside `ValidateMixin` actually delegates the call to `LogMixin.process()`.

Common mistakes

Glossary

Mixins
Small, focused classes designed to provide specific features to other classes, rather than standing on their own. For example, `class LogMixin` adds a `log()` method to any class that inherits it.
composition
A design principle where complex objects are built by combining simpler, independent parts together. For example, `class Service(AuthMixin, LogMixin)` composes two independent behaviors.

Recall questions

Understanding checks

What will be printed when this code is executed?

B C A

The `MRO` for `D` is `[D, B, C, A, object]`. When `D().ping()` is called, it resolves to `B.ping()`. `B` prints `B`, then calls `super().ping()`. Because the instance is of type `D`, the next class in `D`'s `MRO` after `B` is `C`, so `C.ping()` is called. `C` prints `C` and calls `super().ping()`, which delegates to `A`, printing `A`.

A developer states: 'When `super()` is used inside a class method, it always calls the method of the class that was specified as its parent in the class definition.' Why is this false in Python?

`super()` delegates to the next class in the Method Resolution Order (`MRO`) of the calling instance, which may be a sibling class in a multiple inheritance hierarchy, rather than the static parent.

Python's `MRO` is dynamically built based on the class of the object that initiated the method call. This allows mixins to cooperatively call each other even if they don't explicitly inherit from one another.

Practice tasks

Mixin Resolution Order

We have a `ProtectedResource` that currently checks authentication before rate limiting. Modify the inheritance order of `ProtectedResource` so that `RateLimitMixin` is resolved before `AuthMixin`. Observe how the `__mro__` changes.

Challenge

Cooperative Middleware Initialization

Write a `BaseMiddleware` class whose `__init__()` prints `Base initialized`. Write a `TracingMiddleware` that inherits from `BaseMiddleware`, prints `Tracing initialized`, and safely calls `super().__init__()`. Write a `MetricsMiddleware` that inherits from `BaseMiddleware`, prints `Metrics initialized`, and safely calls `super().__init__()`. Finally, create a `ProductionMiddleware` class that inherits from `TracingMiddleware` and `MetricsMiddleware` (in that order), prints `Production initialized`, and calls `super().__init__()`. Instantiate `ProductionMiddleware` to observe the `MRO` in action.

Continue learning

Previous: Class Creation and Instantiation

Previous: Single inheritance and super()

Return to Python Roadmap