skip to content

Multiple Inheritance and Mixins

A mixin is a small, usually stateless class that adds behavior sideways instead of deepening a hierarchy, and its position in the bases tuple decides who wins. Interviewers probe whether yours composes cleanly.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

4

What is a mixin class in Python, and how does it differ from a base class?

level: juniorimportance: must knowfreq 60%

answer

  1. Behaviour sideways, not a deeper tree
  2. Never instantiated on its own
  3. Adds capability, not identity
  4. No __init__, no state of its own
  5. The Mixin suffix is only convention

basics

~20 s

A mixin is a small class that contributes methods to other classes through multiple inheritance and is never instantiated on its own. A base class says what an object is; a mixin only adds a slice of behaviour.

solid answer

~50 s

A mixin is a class written to be inherited alongside a real base class, never used alone. It usually defines a few methods, has no `__init__` and no state of its own, and may call `super()` so the rest of the hierarchy still runs. Python has no `mixin` keyword: it is a convention, signalled by a `Mixin` name suffix and by review discipline. The difference from a base class is intent and completeness. `class LabSample(AsDictMixin, Sample)` reads as *is a* `Sample` that *also knows how to* produce a dict; an instance genuinely is a `Sample`, while calling it an `AsDictMixin` means nothing. Because every base shares one attribute namespace on `self`, a mixin that touches attributes it did not create is making an undeclared contract with its host, which is why stateless mixins compose best.

code

python · 16 lines
python
class AsDictMixin:
    def as_dict(self):
        return {k: v for k, v in vars(self).items() if not k.startswith("_")}


class Sample:
    def __init__(self, sample_id, value):
        self.sample_id = sample_id
        self.value = value


class LabSample(AsDictMixin, Sample):
    pass


print(LabSample("S-1200", 4.2).as_dict())

go deeper

for a junior

Be ready to define a mixin in one sentence and give an example of combining one with a concrete class. Know that it is never instantiated by itself and that the naming convention, not the language, marks it.

for a middle

Explain the mechanics: all bases share one self, methods are found along the class's resolution order, and a mixin with state or an __init__ imposes cooperation on every host. Show how you would test one.

for a senior

An interviewer expects you to talk about the implicit contract a mixin makes with its host, how it breaks under __slots__ or a colliding attribute name, and how you make that contract explicit rather than hopeful.

for a principal

Own the guidance: when a shared capability deserves a mixin at all, when it belongs in the one class that uses it, and how you stop a codebase from growing a wide, undocumented mixin vocabulary nobody can compose confidently.

## A mixin is a class you never instantiate Python's `class` statement accepts any number of bases, and every base contributes its methods to one lookup chain on the resulting class. A mixin exploits that: it is a small class holding a few methods that make sense for many otherwise unrelated classes, and it is mixed into each one by naming it in the bases tuple. Read `class LabSample(AsDictMixin, Sample)` out loud and you get the intent: a `Sample` that also knows how to turn itself into a dict. ## Nothing in the language marks a class as a mixin There is no keyword, no decorator, no interpreter check. `AsDictMixin` is an ordinary class deriving from `object`, and the `Mixin` suffix is a naming convention that tells a reader three things: 1. do not instantiate this, 2. do not subclass it on its own, 3. expect it to be combined with something concrete. Because the convention is not enforced, the discipline has to live in the mixin's own design and in code review. ## Base class versus mixin - A **base class** answers *what is this object*. It normally owns `__init__`, holds the instance's state, and is complete enough to instantiate and use by itself. - A **mixin** answers *what else can this object do*. It normally has no `__init__`, holds no state of its own, and is useless alone because its methods reach for attributes and methods that the host class supplies. That asymmetry also explains the naming: subclassing a base is a statement about identity, adding a mixin is a statement about capability. ## Why mixins are kept stateless All bases of a class share one instance namespace: there is a single `self` and, unless `__slots__` is in play, a single instance dictionary behind it. - Two mixins that each decide to keep a `_cache` attribute silently collide on the same key, and neither author will see it in review because neither file mentions the other. - Worse, a mixin that needs its own initialisation forces every host class to call it correctly through the cooperative-initialisation chain, so a class that just wanted one extra method has inherited an ordering obligation. A mixin that only reads what the host already has, or that calls methods it explicitly declares it requires, is easy to place, easy to reason about and easy to remove. If some shared behaviour genuinely needs its own state and lifecycle, that is usually the signal to make it a separate object the class holds rather than a class the object inherits from. ## Every mixin carries an implicit contract `AsDictMixin` calling `vars(self)` quietly assumes the host keeps its data in an instance dictionary, so it degrades on a host defined with `__slots__`. The fix is to make the requirement explicit rather than hopeful: document what the host must provide, or declare the required method as an abstract method so a host that forgets it fails at instantiation instead of at runtime. When you review someone's mixin, the first question worth asking is not *what does it add* but *what does it silently require*. ## The standard library uses the pattern - `socketserver.ThreadingMixIn` is the textbook example: it supplies the thread-per-request behaviour and is combined with a concrete server class that supplies everything else. - Several abstract base classes in `collections.abc` follow the same shape, deriving a set of concrete methods from the handful you implement yourself. ## A mixin is not the only way to share a method Before reaching for one, ask whether a plain module-level function taking the object as an argument would do. A function has no ordering question, no shared namespace, no hidden contract beyond its parameters, and it is trivially testable. The mixin earns its place when the behaviour must be *dispatchable*: - when subclasses should be able to override it, - when it belongs on the instance's own surface because callers expect `obj.as_dict()`, - or when it must cooperate with a method the host already defines. If none of those apply, a function in a helper module is the smaller tool. ## Practical consequences worth knowing early - Ordering matters, because attribute lookup takes the first definition it finds along the class's method resolution order, which is why mixins are conventionally listed before the concrete base. - Testing a mixin means composing it with a minimal throwaway host class inside the test rather than instantiating the mixin directly. - And a mixin used by exactly one class is not a mixin at all: it is code that belongs in that class, split across two files for no benefit. The pattern earns its keep when the same behaviour is genuinely wanted by several classes that do not otherwise share an ancestor.

  • Why do mixins usually avoid defining __init__?
    Because initialisation is cooperative: once a mixin has an `__init__`, every host class and every other base has to route through the chain correctly and pass the arguments it expects. That turns a one-method capability into an ordering obligation for the whole hierarchy. A mixin with no `__init__` can be added or removed from a bases tuple without touching construction at all.
  • How would you make explicit what a mixin requires from its host class?
    Declare the requirement instead of assuming it: give the mixin an abstract method for the hook it calls, so a host that fails to implement it cannot be instantiated. Failing that, document the required attributes and methods in the mixin's docstring and cover the combination with a test. The worst option is a mixin that reads `self.something` it never defined and never mentioned.
  • How do you unit-test a mixin that cannot be instantiated?
    Compose it inside the test with a minimal host class that provides only the contract the mixin needs, then exercise the combined class. That keeps the test honest about the mixin's requirements: if the throwaway host has to grow, the mixin's implicit contract is bigger than advertised.

A base class is the vehicle; a mixin is a roof rack. The rack is useless on its own, bolts onto many different vehicles, and should not need its own engine.

saying these in an interview costs you the question

  • Thinks Python has a dedicated mixin keyword or declaration
  • Describes a mixin as an interface with no method bodies
  • Gives every mixin its own __init__ and instance state
  • Instantiates the mixin directly instead of composing a host
  • Assumes the Mixin name suffix is enforced by the interpreter
  • Uses a mixin for behaviour only one class ever needs

context

open as a page

Why is a mixin listed before the concrete base class in a Python class statement?

level: middleimportance: must knowfreq 55%

basics

~20 s

Attribute lookup walks the class's method resolution order left to right, so a mixin listed after the concrete base is shadowed by it. Putting the mixin first lets its method win, and its super() call still reaches the base.

open as a page

A mixin's method stops running after a class's bases are reordered in a clinical-lab result loader. How do you diagnose it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Print the class's __mro__ and the method's __qualname__ to see which class actually supplies it. After the reorder the concrete base precedes the mixin and wins lookup, so the override never runs and nothing raises — the behaviour just reverts.

open as a page

When do you stop adding mixins to a Python class and delegate to a collaborator object instead?

level: principalimportance: should knowfreq 35%

basics

~20 s

When the shared behaviour needs its own state or lifecycle, when mixins start reaching for attributes they do not create, or when their method names begin to collide. Delegation costs forwarding code but gives each piece an independent, swappable object.

open as a page