PythonMixins & Multiple Inheritance

Mixins & Multiple Inheritance

A mixin is a small, focused class that adds exactly one piece of behavior — logging, serialization, comparison helpers — and is never meant to be used on its own. It's designed to be combined with another class through inheritance, contributing its behavior without being a "real" standalone type in the program.

Unlike Java, which restricts a class to extending exactly one superclass, Python supports true multiple inheritance: a class can inherit from several base classes at once by listing them all in the class definition, like class Foo(Base, MixinA, MixinB):. This is precisely what makes the mixin pattern possible in Python.

Method Resolution Order (MRO)

When a class inherits from multiple parents, Python needs a consistent rule for which class's version of a method wins if more than one parent defines it. That rule is the Method Resolution Order (MRO) — the specific sequence of classes Python searches, in order, when looking up an attribute or method. You can inspect it directly on any class with ClassName.__mro__, or read a friendlier version via help(ClassName).

Python computes the MRO using an algorithm called C3 linearization. Without going into its internals, the intuition is enough: it merges each parent's own MRO from left to right, preserving each parent's internal ordering, which guarantees that a parent class always appears before its own ancestors in the final order. The result is deterministic — for a given class hierarchy, the MRO is always the same.

A Practical Mixin: SerializableMixin

Here's a mixin that adds a to_json() method to any class, based only on that instance's own __dict__ (the dictionary Python uses internally to store instance attributes). It doesn't define __init__ or know anything about what it's mixed into — it just assumes the instance has some attributes to serialize.

Python
import json


class SerializableMixin:
    def to_json(self):
        return json.dumps(self.__dict__)


class User(SerializableMixin):
    def __init__(self, username, email):
        self.username = username
        self.email = email


class Product(SerializableMixin):
    def __init__(self, name, price):
        self.name = name
        self.price = price


user = User("ada", "ada@example.com")
product = Product("Keyboard", 49.99)

print(user.to_json())
print(product.to_json())
{"username": "ada", "email": "ada@example.com"}
{"name": "Keyboard", "price": 49.99}

User and Product are otherwise unrelated classes — one represents a person, the other a catalog item. Mixing in SerializableMixin gives both of them .to_json() for free, with zero duplicated code between them.

Checking the MRO

Python
print(User.__mro__)
(<class '__main__.User'>, <class '__main__.SerializableMixin'>, <class 'object'>)

This confirms the lookup order: Python checks User first, then SerializableMixin, then falls back to object. If User had defined its own to_json, that version would win, since User comes before the mixin in the MRO.

Mixin Conventions
  • A mixin adds one specific, focused behavior — not a general-purpose base to build a whole hierarchy on.

  • A mixin is not meant to be instantiated by itself; it only makes sense combined with another class.

  • A mixin usually does not define __init__, so it doesn't interfere with the main class's own constructor.

  • Mixin class names conventionally end in "Mixin" so their purpose is obvious at a glance.

Warning
Multiple inheritance can get fragile once hierarchies get deep. A classic issue is the "diamond problem": if two parent classes both inherit from a common ancestor, and both override the same method, it can be ambiguous which version a child class should get. Python's MRO resolves this deterministically — there's always a well-defined answer — but a deterministic answer isn't automatically an obvious or intuitive one to a reader. Tracing through several levels of multiple inheritance to figure out which method actually runs can be genuinely difficult. Keep mixins small, focused on one behavior, and shallow — avoid stacking many mixins on top of already-deep class hierarchies.
Tip
If a method behaves unexpectedly in a class using multiple inheritance, printing `ClassName.__mro__` is often the fastest way to see exactly which class Python will check, and in what order, before you start debugging deeper.