Mixins offer a powerful way to reuse code across multiple Python classes without forcing them into a rigid inheritance hierarchy. Instead of building deep and brittle class trees, you can use mixins to share common behaviors in a modular and flexible way. Learn when and how to implement mixin classes effectively in your Python projects.
By the end of this tutorial, you’ll understand that:
- Mixins allow you to add isolated, reusable functionalities to classes without enforcing a strict type hierarchy.
- Python has no dedicated syntax for declaring mixins. You create mixins by defining classes with specific behaviors that other classes can inherit without forming an is-a relationship.
- Mixins rely on multiple inheritance to combine features from different classes, enhancing flexibility and code reuse.
- Stateful mixins require careful design to manage instance attributes and avoid conflicts with other classes.
- Python’s method resolution order (MRO) determines the order in which classes are inherited, affecting how mixins are applied.
To get the most out of this tutorial, you should have a good understanding of object-oriented programming (OOP), SOLID principles, inheritance, and Python classes.
So, what is a Python mixin, and when should you use one?
Get Your Code: Click here to download the free sample code that shows you how to use mixin classes in Python.
Take the Quiz: Test your knowledge with our interactive “What Are Mixin Classes in Python?” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
What Are Mixin Classes in Python?Test your knowledge of Python mixins—specialized classes that let you reuse methods without traditional inheritance.
In Short: Mixins Encapsulate Reusable Behaviors
In object-oriented programming (OOP), a mixin is a tool that allows you to reuse a common piece of functionality across several, often unrelated data types while keeping them loosely coupled. In practical terms, mixins can help you achieve a cleaner design with more flexible code consisting of modular and composable pieces.
Many programming languages, including Ruby, Dart, and Scala, support this pattern explicitly through specialized syntax and keywords. Others let you simulate mixins with existing constructs, such as default method implementations in Java interfaces. Conversely, Python relies on multiple inheritance as the underlying mechanism to facilitate the same concept.
Python mixins are ordinary classes with a special meaning. You use them to give other classes new superpowers or to modify their current behavior without forming a rigid type hierarchy. Mixins can also act as a form of dependency injection, allowing you to customize code beyond your control, for example, by plugging into a third-party library or framework.
To give you an idea of when mixin classes are most useful, imagine that you wanted to represent arbitrary Python objects with different data formats. Implementing the same serialization logic in each class would lead to duplicate code, which is a well-known code smell that violates the DRY principle:

Notice the repeated .serialize()
method, which appears across all classes in the UML class diagram above. You can fix this issue by extracting the shared functionality into a common base class and creating the following inheritance hierarchy:

But now you end up with an awkward inheritance hierarchy where animals and vehicles share a common ancestor. You’ve grouped them under a shared interface for structural reasons, while they conceptually belong to entirely different categories.
Moreover, a design centered around inheritance can be fragile, making it hard to accommodate future use cases. Maybe you’ll need to implement a mechanical bird equipped with a vehicle’s engine and the ability to fly, but without needing to eat or having a VIN number. As it stands, there’s no straightforward way to fit that kind of data type into your class hierarchy without making inelegant compromises.
Another issue that sometimes arises from using inheritance is a tendency for overgeneralization. When the base class becomes too broad in scope, subclasses start to inherit unnecessary features. For example, if a vehicle requires another serialization method, then all animals automatically inherit it, whether they need one or not. This problem was aptly described by Joe Armstrong, the creator of Erlang, in an interview for Coders at Work:
Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.
— Joe Armstrong
A better approach would be to design your types based on what they do rather than what they are. This means favoring composition over inheritance and delegating responsibilities to smaller, more focused components, promoting separation of concerns:

Compared to the inheritance-based version, this diagram looks much more intimidating due to the greater number of individual components. On the other hand, such a fine-grained design allows you to compose behaviors in novel ways, which could’ve been difficult to anticipate during the initial requirements analysis.
Wouldn’t it be ideal to combine the convenience of inheritance with the flexibility of composition? That’s precisely where mixins shine! Now that you understand the broader context, it’s time to dig a little deeper by answering more specific questions about mixins.
How Can You Recognize a Mixin Class in the Wild?
For starters, here’s a straightforward example of a mixin class defined in Python:
class SerializableMixin:
def serialize(self) -> dict:
if hasattr(self, "__slots__"):
return {
name: getattr(self, name)
for name in self.__slots__
}
else:
return vars(self)
This class defines one method, which returns a dictionary representation of an object, handling two cases. If the object uses .__slots__
, then .serialize()
builds a dictionary using a comprehension expression. Otherwise, the method returns an object’s .__dict__
attribute by calling vars()
on it.
Note: This sample implementation of object serialization cuts corners by ignoring certain edge cases for brevity. Sometimes, an object may have both a .__dict__
and .__slots__
attributes, for example.
The example above demonstrates some of the key takeaways about Python mixins. In particular, you can recognize a mixin class by the following traits:
- Naming convention: That’s the most obvious giveaway. While not mandatory, it’s considered Pythonic to include a distinctive suffix in the class name. This clearly communicates the special purpose of your class and its intended use since Python has no dedicated syntax for mixins. Additionally, its name should reflect the feature provided by the mixin, like the ability to serialize an object.
- Plain class: Because mixins are meant to provide isolated, reusable features—rather than to enforce an inheritance structure—they typically don’t have any parent classes. This minimizes their scope and reduces the likelihood of incorrect name resolution in multiple inheritance scenarios.
- Single behavior: A mixin class has just one responsibility, making it reusable and composable with other mixins—as long as those mixins provide orthogonal or non-overlapping features. Note that this doesn’t necessarily mean that your mixin must only define a single method.
- Statelessness: Mixins rarely define constructors or instance attributes of their own, making them safe to integrate with other classes through multiple inheritance without conflicts. The behavior encapsulated by a mixin class depends solely on an external state. Mixins often rely on attributes of the classes they’re mixed into.
- Non-standalone: Mixins are designed to augment other classes with new or modified behaviors. They typically don’t make any sense independently, so you almost never instantiate mixin classes directly.
Okay. Now that you’re familiar with this high-level checklist, you can delve into each of these concepts by looking at some hands-on examples.
What Makes a Python Class Qualify as a Mixin?
There’s no syntactical difference between standard classes and mixin classes—the distinction is purely semantic. Because Python provides no dedicated syntax for declaring and using mixins, most developers follow well-known naming conventions to communicate their intended role. Below are two examples of mixins, drawn from the Python standard library and the popular Django web framework:
As you can see, both classes have descriptive names, which include either the MixIn
or Mixin
suffix. This pattern makes their purpose clear at a glance for anyone reading the code.
More importantly, a mixin class has a single responsibility. It defines and implements exactly one behavior, which other classes can reuse by means of class inheritance. When you include multiple mixins in a class, you compose several orthogonal or independent features in a conflict-free way.
Notice the emphasis on the word “include” in the last sentence, which is often mentioned when referring to mixins. Even though applying Python mixins boils down to using inheritance, extending a mixin doesn’t inherently create a subtyping relation between the parent and the child class. How come?
Inheritance can generally be understood in two ways:
- As a mechanism for code reuse
- As a tool to establish a subtyping relationship
These two uses are also known as implementation inheritance and interface inheritance, respectively. In Python, you typically use inheritance for both purposes at the same time—to reuse members of the base class and to define its more specialized subtype that can replace the parent type where applicable.
In contrast, mixins are primarily focused on reusing code without implying a strong is-a relationship. Conceptually, classes that extend mixins don’t become their subtypes. Instead, they borrow their functionality. Because mixins aren’t intended to be used polymorphically, treating them as interchangeable with their base classes often violates the Liskov substitution principle.
This leads you to another key observation about mixins. Unlike regular classes, which can exist independently, mixin classes typically don’t. As non-standalone entities, mixins aren’t meant to be instantiated. They’re merely a lightweight ingredient designed to be combined—or mixed in—with other classes to enhance their behavior. Creating an instance of a mixin is pointless at best, and may even raise an error in some cases.
If this sounds familiar, then that’s because mixins resemble abstract base classes (ABCs) to a degree. You’ll now compare the two to get a better understanding of their unique roles in object-oriented programming.
How Do Mixins Compare to Abstract Base Classes?
Mixins and abstract base classes share one common characteristic: both serve as building blocks meant to be combined with one or more target classes through inheritance. Consequently, you can’t use them in isolation because they have no purpose on their own.
As an example, consider the mixin class below, which implements JSON serialization of objects with a .__dict__
attribute. Notice that the underlying behavior depends on attributes defined elsewhere—that’s by design:
>>> import json
>>> class JSONSerializableMixin:
... def as_json(self) -> str:
... return json.dumps(vars(self))
...
>>> mixin_instance = JSONSerializableMixin()
>>> mixin_instance.as_json()
'{}'
Although Python doesn’t prevent you from creating instances of such a mixin class directly, the resulting objects aren’t particularly useful by themselves because they lack attributes of their own. As a result, calling mixin_instance.as_json()
produces an empty JSON object ({}
) encoded as a Python string.
Note: Mixins must either remain stateless or manage their state carefully to reduce the risk of interference with the data attributes of the classes they’re mixed into. So, you’ll rarely find a custom constructor—the .__init__()
method—in a mixin class.
To truly benefit from your mixin, you should integrate it into another class with its own attributes, such as this one:
>>> from dataclasses import dataclass
>>> @dataclass
... class User(JSONSerializableMixin):
... user_id: int
... email: str
...
>>> User(555, "jdoe@example.com").as_json()
'{"user_id": 555, "email": "jdoe@example.com"}'
By extending the JSONSerializableMixin
, you inherit the .as_json()
method, which brings the capability to serialize instances of the User
data class into a JSON string.
At first glance, this approach might seem no different than conventional inheritance in Python. However, because mixins don’t constitute standalone data types, the User
class in the example above doesn’t become a more specialized subtype of JSONSerializableMixin
. Instead, the derived class acquires additional functionality.
A mixin is merely a vessel for delivering one specific behavior that many classes can share without establishing an is-a relationship. In a sense, mixins serve as partial implementations, offering reusable functionalities without the baggage and constraints of interface inheritance.
Note: Although mixin classes implement a single behavior, they may occasionally consist of more than one method. As long as those methods cooperate with each other, they collectively provide a cohesive functionality:
import json
from typing import Self
class JSONSerializableMixin:
@classmethod
def from_json(cls, json_string: str) -> Self:
return cls(**json.loads(json_string))
def as_json(self) -> str:
return json.dumps(vars(self))
Here, you’ve added a class method to your mixin, allowing the target class to deserialize a JSON string into its respective instance. Both methods, .from_json()
and .as_json()
, enable two-way conversion between JSON strings and Python objects. Together, they represent one logical feature.
To help achieve their agnostic design, most mixins are typically implemented as simple Python classes without extending any parent type—much like plain old Java objects (POJO). Additionally, they make few assumptions about the classes they’ll eventually be mixed into. This keeps them largely unaware of their potential descendants, allowing for greater flexibility.
You can safely include one mixin in completely unrelated data types across diverse contexts. For example, you might reuse your JSONSerializableMixin
in a class representing an application’s configuration, like so:
>>> from pathlib import Path
>>> from types import SimpleNamespace
>>> class AppSettings(JSONSerializableMixin, SimpleNamespace):
... def save(self, filepath: str | Path) -> None:
... Path(filepath).write_text(self.as_json(), encoding="utf-8")
...
>>> settings = AppSettings()
>>> settings.host = "localhost"
>>> settings.port = 8080
>>> settings.debug_mode = True
>>> settings.log_file = None
>>> settings.urls = (
... "https://192.168.1.200:8000",
... "https://192.168.1.201:8000",
... )
>>> settings.save("settings.json")
You define a subtype of SimpleNamespace
, which is a lightweight, mutable container with a clean string representation and useful defaults. It lets you dynamically add or remove attributes from its instances and access them through the vars()
function.
Note: As the name implies, SimpleNamespace
should only be used in simple use cases. While it’s quick and convenient for prototyping, it can lead to errors due to naming typos or missing attributes. In the real world, you’d want to represent your configuration with more robust tools, such as data classes or a TypedDict
, which offer better structure and validation.
Despite sharing an ancestor, neither AppSettings
nor your earlier User
data class should be considered part of the same lineage. Remember that you’re using Python’s class inheritance here solely for code reuse, without enforcing a strict parent-child relationship. Formally, the JSONSerializableMixin
remains detached from the type tree.
This starkly contrasts with abstract base classes (ABCs), which are base classes meant to be subclassed and used polymorphically. They participate in subtyping and often have parent classes themselves. The word abstract means that ABCs declare abstract methods without implementing them to establish a contract for subclasses. At the same time, ABCs may provide partial implementations through regular methods and can maintain internal state.
Furthermore, declaring an ABC requires the use of either a special metaclass or a decorator, which Python can leverage to run additional checks for you:
>>> from abc import ABC, abstractmethod
>>> from typing import Any
>>> class Serializer(ABC):
... @abstractmethod
... def serialize(self, data: Any) -> str:
... pass
...
>>> abc_instance = Serializer()
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class Serializer
⮑ without an implementation for abstract method 'serialize'
As long as a class contains abstract or unimplemented methods, you won’t be able to instantiate it without triggering a runtime error. This mechanism enforces the contract on any concrete subclass.
Note: Python provides an alternative way to define contracts without using abstract base classes or inheritance. If you don’t require runtime type checks and code reuse, then consider specifying the expected behavior structurally with a Python protocol.
To sum up, you use abstract base classes to define the core implementation of an algorithm while enforcing a common interface across subclasses. This makes ABCs especially well-suited for implementing the template method pattern. Mixins, on the other hand, provide independent features that you can optionally add to other classes without altering their primary inheritance structure.
While abstract base classes frequently rely on single inheritance to define a clear interface and behavior, mixins often leverage multiple inheritance to seamlessly compose many features. But do you always need multiple inheritance to use mixins in Python? You’ll find out next.
Do Mixins Require Multiple Inheritance in Python?
By now, you know that mixins are designed to provide additional behaviors to other classes through inheritance without being standalone classes by themselves. You’ve also seen two basic examples, demonstrating how mixins can be used with both single and multiple inheritance:
# Single inheritance:
class User(JSONSerializableMixin):
...
# Multiple inheritance:
class AppSettings(JSONSerializableMixin, SimpleNamespace):
...
In the first example, the User
class has only one parent—the mixin itself. But in the second example, the AppSettings
class inherits from the same mixin and another base class at the same time.
Note: Conceptually, AppSettings
is a more specialized type of SimpleNamespace
with an additional capability to serialize itself to JSON. The JSONSerializableMixin
has no knowledge of AppSettings
, User
, or any other descendant type that you may potentially plug it into.
The latter case is generally more common. After all, the main idea behind mixins is to include lightweight, isolated, and reusable features in classes that already extend one or more superclasses without disrupting their existing type hierarchies.
It’s also entirely possible for a class to simultaneously include more than one mixin, effectively letting you fuse several independent behaviors into a single class. That’s precisely what Python’s multiple inheritance enables—combining methods and attributes of more than one parent class. Many Python frameworks leverage this pattern a lot.
Here’s a sample implementation of a web page that displays a list of books in a hypothetical Django application:
django-book-project/library/views.py
from django.views.generic import ListView
from django.contrib.auth.mixins import (
LoginRequiredMixin,
PermissionRequiredMixin,
)
from .models import Book
class BookListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = Book
paginate_by = 10
permission_required = "library.view_book"
In this code snippet, you use mixins with class-based views to compose the desired functionality from modular behaviors declaratively. Instead of repeating similar authentication and authorization code across multiple views, you leverage mixins provided by Django, which abstract away the common logic.
Note: As a rule of thumb, mixins shouldn’t inherit anything from parent classes to remain self-contained and flexible. In some cases, however, you may find it convenient to collect their common parts into a base class. That’s the case with Django’s LoginRequiredMixin
and PermissionRequiredMixin
, both of which extend AccessMixin
to reuse the same helper methods and attributes.
In summary, you don’t strictly need to rely on multiple inheritance to benefit from mixins in Python. That being said, mixins are most effective when you include them alongside other base classes, which can sometimes lead to surprising behavior.
Next up, you’ll learn about some of the dangers of working with mixins in Python and how to cope with them.
What Are the Common Pitfalls of Using Python Mixins?
Beneath their deceptively straightforward appearance, mixins introduce subtle complexities that can trip up even experienced developers. In this section, you’ll review some of the most common challenges associated with Python mixins. You’ll also learn how to mitigate them.
Going Overboard With Mixins
Like every tool in software engineering, mixins are a double-edged sword. When used skillfully, they improve code reuse, separation of concerns, and maintainability, promoting an elegant, declarative style. At the same time, it’s easy to overdo them, leading to a poor design with diminishing returns in the long run.
If you’ve worked on enough Django projects, then chances are you’ve come across code that looks something like this:
django-book-project/library/views.py
# ...
class BookOverloadedView(
LoginRequiredMixin,
PermissionRequiredMixin,
FormMixin,
SingleObjectMixin,
MultipleObjectMixin,
FilterView,
ExportMixin,
AuditLogMixin,
AjaxResponseMixin,
OptimisticLockingMixin,
UpdateView,
):
model = Book
form_class = BookForm
permission_required = "library.change_book"
filterset_class = BookFilter
success_url = "/books/"
Spotting so many mixins in one place is a good indicator of a God object with too many responsibilities that would be better off refactored. For instance, you might decouple it by grouping related functionalities that are frequently used together into higher-level classes.
Including more than a few mixins at a time increases your code’s complexity, making it harder to understand and debug. Imagine trying to follow the flow of execution in this codebase, for example, to determine where a given method was implemented. Because of an extremely tangled inheritance hierarchy, that would be a non-trivial task.
Even worse, what if some of those mixins canceled each other out or stepped on each other’s toes? The observed behavior would depend on the order of their declaration, which is due to how method resolution works in Python. This brings you to another common risk associated with mixins, which you’ll look into now.
Ordering Mixins Incorrectly
Most mixins encapsulate orthogonal behaviors, meaning they operate independently and can typically be combined in any order. Occasionally, though, mixins may introduce conflicting implementations or rely on superclass methods being invoked in a specific sequence. This requires careful attention to their order in the inheritance list.
The two mixins you saw earlier, LoginRequiredMixin
and PermissionRequiredMixin
, are designed to work seamlessly together, no matter how you arrange them in the class definition. While both override the same .dispatch()
method, they actively participate in cooperative multiple inheritance by calling Python’s super()
. As a result, they form a chain of method calls to ensure that each mixin’s logic gets executed at some point.
Nevertheless, the official Django documentation recommends always including LoginRequiredMixin
before any other mixins that could be triggered prematurely:
When using class-based views, you can achieve the same behavior as with
login_required
by using theLoginRequiredMixin
. This mixin should be at the leftmost position in the inheritance list. (Source)
That sounds logical because Python follows base classes from left to right. After all, you’d want to check if the user has logged in before running any subsequent checks related to that user. Consider the following example, which violates this advice:
django-book-project/library/views.py
# ...
class BookDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView):
model = Book
def test_func(self):
return self.request.user.email.endswith("@realpython.com")
Notice that LoginRequiredMixin
appears second in the class declaration, just after UserPassesTestMixin
. This mixin allows you to define a custom test that determines whether a user has permission to access the view. In this case, you verify that the user’s email address belongs to the @realpython.com
domain.
The trouble with this code is that anonymous users in Django don’t come with an .email
attribute. So, when an unauthenticated user tries to access this view, your test function will raise an AttributeError
, resulting in an internal server error (HTTP 500) being displayed in the browser.
The preferred behavior would be to redirect unauthenticated users to the login page, giving them a chance to prove their identity before proceeding to the email domain check. You can achieve this goal by flipping the two mixin declarations in your view:
django-book-project/library/views.py
# ...
class BookDetailView(LoginRequiredMixin, UserPassesTestMixin, DetailView):
model = Book
def test_func(self):
return self.request.user.email.endswith("@realpython.com")
Now, the web framework will ensure that the user is authenticated before checking their email address. In the worst-case scenario, after logging in, the user won’t have sufficient permissions to access the view and will receive a forbidden response (HTTP 403) that you can gracefully handle.
Note: The Django documentation features another interesting case, illustrating that it’s impossible to use multiple instances of UserPassesTestMixin
in the inheritance chain:
class TestMixin1(UserPassesTestMixin):
def test_func(self):
return self.request.user.email.endswith("@example.com")
class TestMixin2(UserPassesTestMixin):
def test_func(self):
return self.request.user.username.startswith("django")
class MyView(TestMixin1, TestMixin2, View): ...
Attempting to stack them like this won’t work. Only the first mixin’s .test_func()
will run, overriding the others. You’ll learn more about this in a bit.
Whenever your program’s behavior relies on implicit factors like the ordering of declarations, it makes your code fragile. Unfortunately, it’s an unavoidable aspect of multiple inheritance, which can manifest itself in even more nefarious ways. You’re about to explore a related gotcha of misplacing mixins in the class header.
Placing Mixins After Base Classes
Among the many challenges of using mixins, this is arguably the one that even seasoned Python developers fall victim to. Mixins should generally appear before other base classes in the inheritance list. Such ordering ensures that their methods are correctly invoked, either overriding or augmenting their counterparts in the base classes.
The only exception to this rule is when a mixin provides unique methods that are guaranteed not to conflict with those of other classes.
Note: Python takes advantage of the C3 superclass linearization algorithm (pronounced “C cubed”) to determine a predictable method resolution order (MRO) when multiple inheritance is involved. This provides an elegant solution to the infamous diamond problem, which caused ambiguity in languages like C++ and led Java to adopt only single inheritance.
To illustrate this common mistake, imagine that you were to implement a data container for storing HTTP headers in Python. You may choose to create a custom case-insensitive mapping of key-value pairs—for example, by inheriting from UserDict
as follows:
utils.py
from collections import UserDict
class CaseInsensitiveDict(UserDict):
def __setitem__(self, key: str, value: str) -> None:
super().__setitem__(key.lower(), value)
def __getitem__(self, key: str) -> str:
return super().__getitem__(key.lower())
def __delitem__(self, key: str) -> None:
super().__delitem__(key.lower())
def __contains__(self, key: str) -> bool:
return super().__contains__(key.lower())
def get(self, key: str, default: str = "") -> str:
return super().get(key.lower(), default)
Your CaseInsensitiveDict
overrides several special methods, which the parent class relies on internally, making sure that all keys are stored in lowercase form. That way, you can access and manipulate headers using any capitalization, such as "cookie"
or "CooKIE"
:
>>> from utils import CaseInsensitiveDict
>>> headers = CaseInsensitiveDict()
>>> headers["Content-Type"] = "application/json"
>>> headers["Cookie"] = "csrftoken=a4f3c7d28c194e5b; sessionid=f92e4b7c6"
>>> headers["cookie"]
'csrftoken=a4f3c7d28c194e5b; sessionid=f92e4b7c6'
>>> "CooKIE" in headers
True
Now, say that you want to hook into some of these special methods through a mixin class that prints out useful debug messages:
utils.py
from collections import UserDict
class DebugMixin:
def __setitem__(self, key, value):
super().__setitem__(key, value)
print(f"Item set: {key=!r}, {value=!r}")
def __delitem__(self, key):
super().__delitem__(key)
print(f"Item deleted: {key=!r}")
class CaseInsensitiveDict(UserDict, DebugMixin):
# ...
The DebugMixin
delegates execution to the corresponding superclass methods before printing messages whenever items in a collection are modified. To add this new behavior to your CaseInsensitiveDict
, you append the mixin to the list of base classes in your class definition.
Unfortunately, after restarting the Python REPL and importing the updated utils
module again, you won’t see any change in the observed behavior:
>>> from utils import CaseInsensitiveDict
>>> headers = CaseInsensitiveDict()
>>> headers["Content-Type"] = "application/json"
>>> headers["Cookie"] = "csrftoken=a4f3c7d28c194e5b; sessionid=f92e4b7c6"
>>> del headers["Cookie"]
>>> headers
{'content-type': 'application/json'}
The dictionary still lets you add and remove key-value pairs as expected, but none of the debug messages show up. That’s because the methods defined in your DebugMixin
are never called. Python doesn’t invoke them since UserDict
breaks the chain of delegation by not using super()
. In other words, your mixin class is completely ignored.
You can follow the linearized inheritance chain of a class by inspecting its .__mro__
attribute. Here’s the MRO of your CaseInsensitiveDict
:
>>> CaseInsensitiveDict.__mro__
(<class 'utils.CaseInsensitiveDict'>,
<class 'collections.UserDict'>,
<class 'collections.abc.MutableMapping'>,
<class 'collections.abc.Mapping'>,
<class 'collections.abc.Collection'>,
<class 'collections.abc.Sized'>,
<class 'collections.abc.Iterable'>,
<class 'collections.abc.Container'>,
<class 'utils.DebugMixin'>,
<class 'object'>)
Because UserDict
implements its methods without calling super()
, it doesn’t delegate execution to the next ancestor, breaking the chain. At the same time, DebugMixin
comes much later in the MRO, so its methods are effectively shadowed by those in UserDict
. This means that even though DebugMixin
appears in the inheritance chain, its functionality is bypassed.
The quickest fix would be to swap UserDict
with DebugMixin
in your class definition, as shown on the highlighted line below:
utils.py
from collections import UserDict
class DebugMixin:
# ...
class CaseInsensitiveDict(DebugMixin, UserDict):
# ...
This moves DebugMixin
to the top of the MRO, immediately after CaseInsensitiveDict
and before UserDict
. Consequently, the mixin’s methods will take precedence over the same methods defined in the subsequent ancestors:
>>> from utils import CaseInsensitiveDict
>>> CaseInsensitiveDict.__mro__
(<class 'utils.CaseInsensitiveDict'>,
<class 'utils.DebugMixin'>,
<class 'collections.UserDict'>,
<class 'collections.abc.MutableMapping'>,
<class 'collections.abc.Mapping'>,
<class 'collections.abc.Collection'>,
<class 'collections.abc.Sized'>,
<class 'collections.abc.Iterable'>,
<class 'collections.abc.Container'>,
<class 'object'>)
Because your mixin class now delegates to super()
, each time you add or delete a key-value pair, Python displays a descriptive message in addition to updating the dictionary:
>>> headers = CaseInsensitiveDict()
>>> headers["Content-Type"] = "application/json"
Item set: key='content-type', value='application/json'
>>> headers["Cookie"] = "csrftoken=a4f3c7d28c194e5b; sessionid=f92e4b7c6"
Item set: key='cookie', value='csrftoken=a4f3c7d28c194e5b; sessionid=f92e4b7c6'
>>> del headers["Cookie"]
Item deleted: key='cookie'
>>> headers
{'content-type': 'application/json'}
By strategically placing mixins within the inheritance chain and deciding whether to use the super()
method, you gain control over behaviors defined in the ancestor classes. This powerful technique allows for implementing dependency injection without modifying the original class definitions.
At this point, you know that order matters when using multiple inheritance in Python—a common pattern with mixins. But there’s another important pitfall to be aware of when working with mixins that’s worth exploring.
Defining Uncooperative Constructors
Nine times out of ten, you should refrain from putting state into your mixin classes. Mixins are primarily concerned with providing behaviors rather than managing data. Once you start specifying instance attributes, you risk conflicts with attributes of the concrete classes they’ll be mixed with. And, since you typically define attributes in a custom initializer method, you now invite a host of problems associated with multiple inheritance.
Note: Don’t include an .__init__()
method in your mixin classes unless you have a compelling reason and understand the possible outcomes.
To demonstrate what might go wrong when you store data in a mixin class, consider the following example. Suppose you want to restrict the type of keys in custom mappings to arbitrary data types. You may start by creating a parameterized mixin like this:
mixins.py
class TypedKeyMixin:
def __init__(self, type_):
self.type_ = type_
def __setitem__(self, key, value):
if not isinstance(key, self.type_):
raise TypeError(f"key must be {self.type_} but was {type(key)}")
super().__setitem__(key, value)
The mixin’s initializer method takes the desired key type as a parameter and stores it in an instance attribute named .type_
. Additionally, the .__setitem__()
method checks if the supplied key is an instance of the expected type, and then delegates to the superclass.
Note: Appending a trailing underscore (_
) to Python names is a common practice, which helps avoid clashes with Python keywords, built-in functions like type()
, and other reserved symbols.
Now give your code a test drive by creating an inventory mapping where the keys are restricted to strings only:
>>> from collections import UserDict
>>> from mixins import TypedKeyMixin
>>> class Inventory(TypedKeyMixin, UserDict):
... pass
...
>>> fruits = Inventory(str)
>>> fruits["apples"] = 42
Traceback (most recent call last):
...
AttributeError: 'Inventory' object has no attribute 'data'
Oops! You forgot to call super()
in your initializer method, preventing other base classes from setting themselves up properly. Since your mixin class broke the inheritance chain, UserDict
never created its .data
attribute. Fortunately, there’s an easy fix for that:
mixins.py
class TypedKeyMixin:
def __init__(self, type_):
super().__init__()
self.type_ = type_
def __setitem__(self, key, value):
if not isinstance(key, self.type_):
raise TypeError(f"key must be {self.type_} but was {type(key)}")
super().__setitem__(key, value)
By adding the call to super()
, you ensure that Python triggers UserDict.__init__()
at the right time. You can verify this by rerunning the same code snippet in the Python REPL:
>>> from collections import UserDict
>>> from mixins import TypedKeyMixin
>>> class Inventory(TypedKeyMixin, UserDict):
... pass
...
>>> fruits = Inventory(str)
>>> fruits["apples"] = 42
>>> fruits["🍌".encode("utf-8")] = 15
Traceback (most recent call last):
...
TypeError: key must be <class 'str'> but was <class 'bytes'>
As expected, your inventory only accepts key-value pairs where the key is a string. If you attempt to use a key of any other data type, such as a bytes()
object representing a banana emoji encoded in UTF-8, then Python will raise a TypeError
.
At first glance, this solution seems rock solid. But as you start layering more behaviors through mixins that also rely on an internal state, things can get tricky.
Using Conflicting Instance Attributes
Encouraged by your progress so far, you decide to go ahead and create another mixin that restricts the type of associated values:
mixins.py
# ...
class TypedValueMixin:
def __init__(self, type_):
super().__init__()
self.type_ = type_
def __setitem__(self, key, value):
if not isinstance(value, self.type_):
raise TypeError(f"value must be {self.type_} but was {type(value)}")
super().__setitem__(key, value)
The structure of TypedValueMixin
is nearly identical to that of TypedKeyMixin
, which you defined earlier. The primary distinction is that this mixin validates the type of the value
parameter and emits a slightly different error message.
Sadly, when you include both mixins at the same time in your Inventory
, you’re no longer able to instantiate it using two parameters corresponding to key and value types:
>>> from collections import UserDict
>>> from mixins import TypedKeyMixin, TypedValueMixin
>>> class Inventory(TypedKeyMixin, TypedValueMixin, UserDict):
... pass
...
>>> fruits = Inventory(str, int)
Traceback (most recent call last):
...
TypeError: TypedKeyMixin.__init__() takes 2 positional arguments
⮑ but 3 were given
The error message is a bit misleading, as you need to subtract one from each number. Python expects only one explicitly passed positional argument, with the second one being the implicit self
reference. In contrast, you’re passing two explicit arguments—str
and int
, for the keys and values—in addition to the implicit self
.
The problem with your current implementation lies in how the mixin classes handle initialization. Although they correctly use super()
to delegate control to their ancestors, they fail to forward the required arguments. To resolve this, you must update the method signatures and invocations of super()
so that they can accept and pass through arbitrary arguments:
mixins.py
# ...
class TypedKeyMixin:
def __init__(self, type_, *args, **kwargs):
super().__init__(*args, **kwargs)
self.type_ = type_
# ...
class TypedValueMixin:
def __init__(self, type_, *args, **kwargs):
super().__init__(*args, **kwargs)
self.type_ = type_
# ...
To account for the fact that arguments in Python can be passed using either the positional or keyword syntax, you use *args
and **kwargs
to intercept both. Thanks to that, regardless of how you provide parameters to the constructor, they’ll be correctly received and dispatched.
Note: When you design Python classes to support cooperative multiple inheritance, it’s essential that every class in the hierarchy consistently calls super()
with *args
and **kwargs
to pass along all arguments. It only takes one outlier to disrupt the whole chain!
This change lets you instantiate Inventory
again. However, when you try to add a valid key-value pair, which previously worked fine, you get a rather puzzling complaint from Python:
>>> from collections import UserDict
>>> from mixins import TypedKeyMixin, TypedValueMixin
>>> class Inventory(TypedKeyMixin, TypedValueMixin, UserDict):
... pass
...
>>> fruits = Inventory(str, int)
>>> fruits["apples"] = 42
Traceback (most recent call last):
...
TypeError: value must be <class 'str'> but was <class 'int'>
The error message insists that the value must be a string, even though you clearly declared it as an integer. It turns out that updating the signatures wasn’t enough. As you can see, dealing with bugs can sometimes feel like a game of whack-a-mole. You fix one problem, only to have another one pop up somewhere else!
Your mixins strip and forward values from the parameter lists correctly, but they both store the specified types in the same field (self.type_
), which leads to a name collision. When you inspect your fruits
inventory, you’ll notice there’s only one .type_
attribute:
>>> vars(fruits)
{'data': {},
'type_': <class 'str'>}
Depending on how your mixins are ordered in the MRO, and whether you set the values before or after calling super()
, one assignment effectively overwrites the other. In this case, int
got replaced by str
. This makes distinguishing between the types intended for keys and values impossible.
To avoid ambiguity, it’s best to stick to unique names, or better yet, rely on Python’s name mangling by prefixing attribute names with a double underscore (__
). Also, while not mandatory, choosing more descriptive names like key_type
and value_type
for your parameters will definitely improve readability:
mixins.py
# ...
class TypedKeyMixin:
def __init__(self, key_type, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__type = key_type
def __setitem__(self, key, value):
if not isinstance(key, self.__type):
raise TypeError(
f"key must be {self.__type} but was {type(key)}"
)
super().__setitem__(key, value)
class TypedValueMixin:
def __init__(self, value_type, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__type = value_type
def __setitem__(self, key, value):
if not isinstance(value, self.__type):
raise TypeError(
f"value must be {self.__type} but was {type(value)}"
)
super().__setitem__(key, value)
This makes Python rename both of your .__type
attributes behind the scenes, resulting in two distinct identifiers visible from the outside:
>>> vars(fruits)
{'data': {},
'_TypedValueMixin__type': <class 'int'>,
'_TypedKeyMixin__type': <class 'str'>}
The nice thing about name mangling is that you can still access these attributes directly using their original names, like .__type
, from within their respective classes.
Putting it all together, your stateful mixins finally work as they should:
>>> fruits = Inventory(str, int)
>>> fruits["apples"] = 42
>>> fruits["🍌".encode("utf-8")] = 15
Traceback (most recent call last):
...
TypeError: key must be <class 'str'> but was <class 'bytes'>
>>> fruits["bananas"] = 3.5
Traceback (most recent call last):
...
TypeError: value must be <class 'int'> but was <class 'float'>
Trying to use key or value types other than those declared in the constructor immediately raises exceptions, keeping your inventory in a consistent state.
Now that you know what to avoid when implementing custom mixins in Python, it’s time to break the rules! Next up, you’ll consciously design stateful mixins that can be safely integrated into other classes.
How Can You Use Stateful Mixins Safely?
While it’s generally best to avoid storing data in your mixin classes to prevent some of the pitfalls you’ve just seen, there are situations where you have no choice. Fortunately, you can still approach this problem in safe and Pythonic ways. In this section, you’ll compare three alternative strategies for managing internal state effectively.
Move the State to Class Attributes
You can minimize the hurdles of multiple inheritance by taking a different route. Simply eliminate the initializer methods and replace instance attributes with class attributes, like so:
stateful_v1.py
class TypedKeyMixin:
key_type = object
def __setitem__(self, key, value):
if not isinstance(key, self.key_type):
raise TypeError(
f"key must be {self.key_type} but was {type(key)}"
)
super().__setitem__(key, value)
class TypedValueMixin:
value_type = object
def __setitem__(self, key, value):
if not isinstance(value, self.value_type):
raise TypeError(
f"value must be {self.value_type} but was {type(value)}"
)
super().__setitem__(key, value)
This moves the state from the individual instances—accessed with self
—to the target class. If a class composed of your mixins doesn’t specify these attributes, then Python will fall back to their default values defined in the corresponding mixin classes. In this case, both attributes default to object
.
In turn, you’ll be able to use a declarative style resembling that of the Django mixins you saw earlier to specify your key and value types:
>>> from collections import UserDict
>>> from stateful_v1 import TypedKeyMixin, TypedValueMixin
>>> class Inventory(TypedKeyMixin, TypedValueMixin, UserDict):
... key_type = str
... value_type = int
...
>>> fruits = Inventory()
>>> fruits["apples"] = 42
>>> fruits["🍌".encode("utf-8")] = 15
Traceback (most recent call last):
...
TypeError: key must be <class 'str'> but was <class 'bytes'>
>>> fruits["bananas"] = 3.5
Traceback (most recent call last):
...
TypeError: value must be <class 'int'> but was <class 'float'>
>>> vars(fruits)
{'data': {'apples': 42}}
The key_type
and value_type
attributes aren’t part of the fruits
instance, as confirmed by vars()
, so they won’t interfere with the internal state of other base classes. However, they can still shadow class attributes with the same names.
Although a collision of class attributes is far less likely to happen, you may want to safeguard against it anyway. To make your code immune to such name clashes, consider using a closure.
Hide the State in Function Closures
There’s a more elegant way to create stateful mixins, which is free from the aforementioned problems. Rather than have generic, parameterized mixin classes, you can dynamically synthesize their concrete variants as needed with the help of Python closures:
stateful_v2.py
def TypedKeyMixin(key_type=object):
class _:
def __setitem__(self, key, value):
if not isinstance(key, key_type):
raise TypeError(
f"key must be {key_type} but was {type(key)}"
)
super().__setitem__(key, value)
return _
def TypedValueMixin(value_type=object):
class _:
def __setitem__(self, key, value):
if not isinstance(value, value_type):
raise TypeError(
f"value must be {value_type} but was {type(value)}"
)
super().__setitem__(key, value)
return _
By wrapping class definitions in higher-order factory functions, you create specialized mixins tailored to the supplied parameter. Because this parameter is bound to the function’s lexical scope, each mixin retains its own distinct value without leaking it into others. This prevents unintended sharing of state or cross-contamination between your mixin variants.
Now, here’s how you might use your new mixins in practice:
>>> from collections import UserDict
>>> from stateful_v2 import TypedKeyMixin, TypedValueMixin
>>> class Inventory(TypedKeyMixin(str), TypedValueMixin(int), UserDict):
... key_type = "This attribute has nothing to collide with"
...
>>> fruits = Inventory()
>>> fruits["apples"] = 42
>>> fruits["🍌".encode("utf-8")] = 15
Traceback (most recent call last):
...
TypeError: key must be <class 'str'> but was <class 'bytes'>
>>> fruits["bananas"] = 3.5
Traceback (most recent call last):
...
TypeError: value must be <class 'int'> but was <class 'float'>
>>> vars(fruits)
{'data': {'apples': 42}}
>>> Inventory.key_type
'This attribute has nothing to collide with'
Notice how you call the closures with their respective arguments in the class definition. This dynamically creates new mixins on the spot.
Because the state is encapsulated within the closures rather than stored in the mixins, the target class avoids inheriting potentially conflicting attributes. Additionally, since the mixins don’t define any class attributes of their own, there’s no risk of name collisions at the class level either.
Finally, you can use composition as an alternative to mixins to sidestep multiple inheritance. In the next section, you’ll explore how that works.
Compose Behaviors With Decorators
If this tutorial has left you unconvinced about using mixins, then Python offers several other tools for composing behaviors and promoting code reuse. In particular, decorators provide a flexible and readable way to extend functionality without the complexity of multiple inheritance.
You can adapt your mixin classes by turning them into class decorators that augment the .__setitem__()
method:
stateful_v3.py
def key_type(expected_type):
def class_decorator(cls):
setitem = cls.__setitem__
def __setitem__(self, key, value):
if not isinstance(key, expected_type):
raise TypeError(
f"key must be {expected_type} but was {type(key)}"
)
return setitem(self, key, value)
cls.__setitem__ = __setitem__
return cls
return class_decorator
def value_type(expected_type):
def class_decorator(cls):
setitem = cls.__setitem__
def __setitem__(self, key, value):
if not isinstance(value, expected_type):
raise TypeError(
f"value must be {expected_type} but was {type(value)}"
)
return setitem(self, key, value)
cls.__setitem__ = __setitem__
return cls
return class_decorator
These two decorators, @key_type
and @value_type
, inject new behaviors into a class by replacing its .__setitem__()
method. Each decorator checks whether the key
or value
passed into the dictionary is of the expected type, raising a TypeError
if not.
You can put these decorators into action by stacking them on top of a UserDict
subclass:
>>> from collections import UserDict
>>> from stateful_v3 import key_type, value_type
>>> @key_type(str)
... @value_type(int)
... class Inventory(UserDict):
... key_type = "This attribute has nothing to collide with"
...
>>> fruits = Inventory()
>>> fruits["apples"] = 42
>>> fruits["🍌".encode("utf-8")] = 15
Traceback (most recent call last):
...
TypeError: key must be <class 'str'> but was <class 'bytes'>
>>> fruits["bananas"] = 3.5
Traceback (most recent call last):
...
TypeError: value must be <class 'int'> but was <class 'float'>
>>> vars(fruits)
{'data': {'apples': 42}}
>>> Inventory.key_type
'This attribute has nothing to collide with'
This example works the same as the previous one based on mixin classes. Although the order of decorators generally matters—just like ordering mixins—it has no visible effect in this particular case. This is due to the non-overlapping nature of the encapsulated behaviors.
If you’re feeling adventurous, then why not combine both decorators into a single one so you can apply it in one step? For example:
# Enforce str keys and int values:
@typed_dict(str, int)
class Inventory(UserDict):
pass
# Enforce str keys, allow any value type:
@typed_dict(str)
class AppSettings(UserDict):
pass
Give it a try yourself first, and then compare your solution to the one below:
One possible implementation might look like this:
def typed_dict(key_type=object, value_type=object):
def class_decorator(cls):
setitem = cls.__setitem__
def __setitem__(self, key, value):
if not isinstance(key, key_type):
raise TypeError(
f"value must be {key_type} but was {type(key)}"
)
if not isinstance(value, value_type):
raise TypeError(
f"value must be {value_type} but was {type(value)}"
)
setitem(self, key, value)
cls.__setitem__ = __setitem__
return cls
return class_decorator
How close did your version come?
By using decorators, you gain explicit control, better testability, and improved separation of concerns compared to traditional mixins. This approach avoids method resolution traps and enables safer runtime customization.
On the one hand, decorators may be more verbose, but they offer a powerful and maintainable alternative to mixins when inheritance is either impractical or undesirable.
Conclusion
Mixins are a powerful tool in your object-oriented programming toolkit, enabling you to add functionality to classes in a clean and efficient manner. By understanding the principles and best practices outlined in this tutorial, you can avoid common pitfalls and harness the full potential of mixins to enhance your codebase.
In this tutorial, you’ve learned how to:
- Leverage mixins to write modular, reusable, and composable code
- Create custom mixin classes while avoiding their common pitfalls
- Use multiple inheritance to synthesize new data types with a specific set of behaviors
- Build stateful mixins and use decorators as their safe alternative
Now that you’ve gained a comprehensive understanding of mixins in Python, you’re well-equipped to leverage them in your own projects. Whether you’re building custom behaviors, extending third-party libraries, or refactoring code for better modularity, mixins can help you craft expressive, maintainable, and Pythonic solutions.
Get Your Code: Click here to download the free sample code that shows you how to use mixin classes in Python.
Frequently Asked Questions
Now that you have some experience with mixin classes in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
A mixin in Python is a class that provides methods to other classes through inheritance but isn’t meant to stand alone. You use mixins to add specific behaviors to classes without forming a rigid hierarchy.
You use mixins to achieve code reuse by composing classes with reusable behaviors. Mixins allow you to add functionality to different classes without duplicating code or creating deep inheritance hierarchies.
You identify a mixin class by its naming convention, usually ending with “Mixin,” and by its narrow focus on providing a single behavior without maintaining state. Mixins usually don’t inherit from other classes and aren’t meant to be instantiated on their own.
Mixins can have state, but it’s generally discouraged because it can lead to conflicts in multiple inheritance scenarios. If you need state, then consider using class attributes, closures, or decorators instead.
The risks include potential conflicts in method resolution order, especially if mixins are overused or ordered incorrectly. They can also make debugging and understanding the codebase more difficult if not implemented carefully.
Take the Quiz: Test your knowledge with our interactive “What Are Mixin Classes in Python?” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
What Are Mixin Classes in Python?Test your knowledge of Python mixins—specialized classes that let you reuse methods without traditional inheritance.