Mixins and the method resolution order
A mixin is a small class that adds one behaviour, such as converting to a dictionary, to any class that lists it as a parent. With several parents, Python looks methods up in a fixed order, the MRO, and super() follows it.
- Write a mixin that adds a method to unrelated classes
- Read
Class.__mro__to see the order in which Python looks up methods - Chain mixin methods with
super()so every class in the MRO takes part
Customers, products and orders all need to become dictionaries (for JSON) but have nothing else in common, so a shared parent class would be wrong. A mixin is a small class with one capability and no __init__ of its own; a class gains the capability by listing the mixin among its parents: class Customer(ToDictMixin, JsonMixin):. By convention its name ends in Mixin.
With several parents, Python needs an order for looking up methods: the method resolution order (MRO), visible as Class.__mro__. Roughly: the class itself, then its parents left to right, with each class appearing before its own parents.
Run each example to see what it prints. Running examples doesn't count toward progress.
import json class ToDictMixin: def to_dict(self): return dict(vars(self)) class JsonMixin: def to_json(self): return json.dumps(self.to_dict(), sort_keys=True) class Customer(ToDictMixin, JsonMixin): def __init__(self, customer_id, name, city): self.customer_id = customer_id self.name = name self.city = city class Product(ToDictMixin, JsonMixin): def __init__(self, sku, price): self.sku = sku self.price = price print(Customer("C00077", "Jamal Walker", "Houston").to_dict()) print(Product("SPO-11681", 31.0).to_json()) print([cls.__name__ for cls in Product.__mro__])
When several classes in the MRO define the same method and each calls super().save(...), super() doesn't mean "my parent": it means the next class in the MRO of the object's class. So every mixin runs its part and passes on, and the chain ends at the real implementation. This is called *cooperative* multiple inheritance, and the order of the bases decides the order of the steps.
class Repository: def __init__(self): self.saved = [] def save(self, record): self.saved.append(record) return record class TimestampMixin: def save(self, record): record = dict(record, saved_at="2026-10-20T09:00:00Z") return super().save(record) class AuditMixin: def save(self, record): print("audit: saving", record["order_id"], sorted(record)) return super().save(record) class OrderRepository(AuditMixin, TimestampMixin, Repository): pass repo = OrderRepository() repo.save({"order_id": "O100282", "total": 35.84}) print([cls.__name__ for cls in OrderRepository.__mro__]) print(repo.saved)
class Repository: def save(self, record): return record class AuditMixin(Repository): def save(self, record): print("audit") return super().save(record) print("defining") class OrderRepository(Repository, AuditMixin): pass
When to use it: for small, independent capabilities shared by unrelated classes (serialising, comparing, logging). Mistakes to avoid: mixins with their own state and __init__ (they become hard to combine); a mixin method that overrides without calling super(), which silently cuts the chain; putting the main base class before the mixins; and deep webs of mixins nobody can follow (composition, the Inheritance & Composition module, is often clearer).
- A mixin adds one behaviour to any class that lists it as a base; its name ends in Mixin by convention.
Class.__mro__shows the lookup order: the class, then its bases left to right, each before its own parents.super()means the next class in the object's MRO, so cooperating mixins each run and pass on.- Next: descriptors, reusable attribute logic that powers property itself.