DEV Community

loading...
Cover image for Write composable, reusable Python classes using Mixins

Write composable, reusable Python classes using Mixins

bikramjeetsingh profile image Bikramjeet Singh ・5 min read

An important principle in programming is DRY - Don't Repeat Yourself. It's considered good practice to avoid repeating similar chunks of code in multiple places, and instead define the functionality once - say, in a separate function or class. Then, wherever that functionality is required, you can simply invoke the function or create an instance of the class to do what you need.

Why is DRY important? Because, when you inevitably have to make changes in your code sometime in the future, you want to be able to make those changes at one place, rather than have to hunt through your entire codebase. Not only does this make your life as a developer a lot easier, it also reduces the risk of you missing out on one usage instance and thus creating inconsistent behaviour in your code.

One way to follow the DRY principle in Python is to make use of mixins. Let's see what mixins are and how they help us.

💡 Note: this article assumes you have some understanding of basic OOP (Object Oriented Programming) concepts. If you are not very familiar with OOP yet, don't worry! I've found this article to be very useful in explaining the basics.

Inheritance in Python

On important thing to note is that Python, unlike some other Object-Oriented languages such as Java or C#, permits multiple inheritance. That is, a single class can have multiple parent classes. This is important because, as we'll see very soon, multiple inheritance is the basis for mixins in Python.

What is a Mixin?

Let's say you're baking pizza for your family. The problem is, your entire family is very fussy when it comes to food. Your sister likes her pizza with olives, your father likes it with sausage, your mother likes neither and you like both!

How do you go about cooking 4 different pizzas efficiently? Would you make the toppings for each pizza from scratch, each time?

No! You'd probably first buy all the ingredients you need at once, then grate the cheese, chop up the olives, chop up the sausages and keep them aside in bowls. Then, as you're preparing each pizza, 'mix in' toppings whenever required before slipping it into the oven.

This example leads us to the definition of a mixin: a piece of standalone optional functionality that can be 'mixed in' whenever required. In our example, the toppings (olives and sausages) are both examples of mixins. They are optional - that is, the pizza would taste just fine without them - but their presence acts as a sort of enhancement. The same mixin can be added to multiple pizzas, or multiple mixins can be added to a single pizzas.

How Do I Create a Mixin in Python?

There is no special syntax for creating mixins. Just write a regular Python class.

class OlivesMixin:

    def add_olives(self):
        print("Adding olives!")
Enter fullscreen mode Exit fullscreen mode

Just like any other class, a mixin can extend other classes, if required. It can also be instantiated by itself, but there is usually little reason to do this. Remember, mixins are meant to assist other classes by providing them specific functionality, not exist in isolation.

Now that we have defined our mixin, all we need to do to benefit from it is to make another class inherit it:

class SistersPizza(OlivesMixin, PlainPizza):

    def prepare_pizza(self):
        self.add_olives()
Enter fullscreen mode Exit fullscreen mode

This way, we can make use of the add_olives method anywhere in our class, just as if it was defined inside the class. At the same time, we can take advantage of reusability and use it in any other class we want, just by adding the OlivesMixin mixin!

In its current form, our mixin doesn't provide much utility, because it only consists of a single method that can simply be extracted and written as a standalone function. The real advantage of mixin classes is that, while they cannot contain any internal state of their own, they can manipulate the internal state of the class they are 'mixed' into.

class PlainPizza:

    def __init__(self):
        self.toppings = []


class OlivesMixin:

    def add_olives(self):
        print("Adding olives!")
        self.toppings += ['olives']


class SistersPizza(OlivesMixin, PlainPizza):

    def prepare_pizza(self):
        self.add_olives()
Enter fullscreen mode Exit fullscreen mode

A single class can inherit and make use of multiple mixins. One crucial point to note is that the mixins must always go to the left of the 'base' class, i.e the main parent class should always be the right-most in the list of superclasses.

class MyPizza(OlivesMixin, SausagesMixin, PlainPizza):
    ... 
Enter fullscreen mode Exit fullscreen mode

This is important because of Python's MRO (Method Resolution Order), the priority order in which Python searches classes for a method. You can read about the reasoning behind this here.

Let's Take a Practical Example

Our pizza example is well and good for the sake of introduction, but it's not the kind of code we'd ever need to write in the real world. Let's take a more practical example.

The Django web framework makes extensive use of mixins, both in its internal code and the APIs it exposes.

TemplateResponseMixin & ContextMixin

Generally, if you want to render a template in Django, you'd use the TemplateView class.

class MyView(TemplateView):

    template_name = 'my_template.html'
Enter fullscreen mode Exit fullscreen mode

However, this class only works with GET requests. What if you run into a situation where you have to render a template in response to a POST or PUT request? You can return an individual TemplateResponse in each method ...

class MyView(View):

    def post(self, request, *args, **kwargs):
        return TemplateResponse(
            request=request,
            template='my_template.html'
        )

    def put(self, request, *args, **kwargs):
        return TemplateResponse(
            request=request,
            template='my_template.html'
        )
Enter fullscreen mode Exit fullscreen mode

... but this causes some unnecessary duplication of code, as we are specifying the template name twice.

For this purpose we can use Django's TemplateResponseMixin, which provides the render_to_response method.

class MyView(TemplateResponseMixin, View):

    template_name = 'my_template.html'

    def post(self, request, *args, **kwargs):
        return self.render_to_response(context={})

    def put(self, request, *args, **kwargs):
        return self.render_to_response(context={})
Enter fullscreen mode Exit fullscreen mode

The context parameter above, which is currently empty, allows us to pass a dictionary of context variables, which are dynamically inserted into our template. For this purpose, it is also useful to subclass the ContextMixin, containing the get_context_data method.

class MyView(TemplateResponseMixin, ContextMixin, View):

    template_name = 'my_template.html'

    def post(self, request, *args, **kwargs):
        return self.render_to_response(context=self.get_context_data())

    def put(self, request, *args, **kwargs):
        return self.render_to_response(context=self.get_context_data())

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # Add your context variables here
        return context
Enter fullscreen mode Exit fullscreen mode

TemplateResponseMixin and ContextMixin are almost always found together, since they work well in tandem. You can read more about them here

Conclusion

We've seen how using Python mixins in our classes helps make our code more modular. The resulting code is shorter, simpler to understand and follows DRY best practices. Mixins are used everywhere in the object-oriented Python world, from web development to AI.

I hope you found this article useful. If you spot any errors or would like to offer any suggestions, please feel free to drop a comment below.

Image by joshuemd from Pixabay

References and Further Reading

What is a mixin, and why are they useful?
Using Mixins With Class-Based Views
Multiple inheritance and mixin classes in Python
Djangocon: Demystifying mixins with Django

Discussion

pic
Editor guide