skip to content

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