What Are Mixin Classes in Python?

What Are Mixin Classes in Python?

by Bartosz Zaczyński Publication date Aug 06, 2025 Reading time estimate 44m intermediate python

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?

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:

Class Diagram of Duplicated Serialization Logic
Class Diagram Showing Duplicated Serialization Logic

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:

Class Diagram of an Awkward Inheritance Hierarchy
Class Diagram of an Awkward 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:

Explosion of Classes Due to Composition
Explosion of Classes Due to Composition

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:

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.

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:

  1. As a mechanism for code reuse
  2. 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:

Python
>>> 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.

To truly benefit from your mixin, you should integrate it into another class with its own attributes, such as this one:

Python
>>> 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.

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:

Python
>>> 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.

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:

Python
>>> 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.

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:

Python
# 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.

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:

Python 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.

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:

Python 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 the LoginRequiredMixin. 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:

Python 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:

Python 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.

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.

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:

Python 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":

Python
>>> 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:

Python 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:

Python
>>> 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:

Python
>>> 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:

Python 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:

Python
>>> 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:

Python
>>> 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.

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:

Python 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.

Now give your code a test drive by creating an inventory mapping where the keys are restricted to strings only:

Python
>>> 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:

Python 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:

Python
>>> 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:

Python 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:

Python
>>> 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:

Python 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.

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:

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:

Python
>>> 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:

Python 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:

Python
>>> 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:

Python
>>> 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:

Python 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:

Python
>>> 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:

Python 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:

Python
>>> 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:

Python 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:

Python
>>> 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:

Python
# 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:

Python
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.

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.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Bartosz Zaczyński

Bartosz is an experienced software engineer and Python educator with an M.Sc. in Applied Computer Science.

» More about Bartosz

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!