This post originally appeared on Easy as Python in December 2016.
There are people out there who have the time, motivation, and resources to cook meals entirely from scratch. They'll turn flour, water, and eggs into pasta and turn cheese, cream, and spices into a sauce. These people make delicious food, but it takes time. Can you imagine how much longer it would take if they also milled the wheat, farmed the chickens, and milked the cows themselves?
When creating software, there's sometimes a limit to the depth we should go. When pieces of what we'd like to achieve have already been executed well by others, though, it makes a lot of sense to reuse them. Instead of milling wheat and raising cows, we can buy flour and butchered beef. We can start from scratch, but not scratch scratch.
One way to set ourselves up for success in object-oriented programming is through a concept called a mixin. In Python, mixins are supported via multiple inheritance. Mixins take various forms depending on the language, but at the end of the day they encapsulate behavior that can be reused in other classes.
The delineation between using true inheritance and using mixins is nuanced, but it comes down to the fact that a mixin is independent enough that it doesn't feel the same as a parent class. Mixins aren't generally used on their own, but aren't abstract classes either.
Suppose we've written a slew of Python software and our application is ready to launch. We're about to hit the button when someone comes over and says “what kinds of things are we logging?” Dear God, you think. We aren't logging anything! We could go ahead and add the following boilerplate to all our modules to get started with logging:
import logging class EssentialFunctioner: def __init__(self): ... self.logger = logging.getLogger( '.'.join([ self.__module__, self.__class__.__name__ ]) ) def do_the_thing(self): try: ... except BadThing: self.logger.error('OH NOES')
That's not too bad, but imagine doing this to twenty or thirty classes. Not fun. How can mixins help? Have a look. Let's create a
LoggerMixin that does the same work as above:
import logging class LoggerMixin: @property def logger(self): name = '.'.join([ self.__module__, self.__class__.__name__ ]) return logging.getLogger(name)
With this nicely encapsulated, we can now go around adding the following to our existing code:
class EssentialFunctioner(LoggerMixin): def do_the_thing(self): try: ... except BadThing: self.logger.error('OH NOES') class BusinessLogicer(LoggerMixin): def __init__(self): self.logger.debug('Giving the logic the business...')
We wrote the functionality just once, but now we can use it everywhere! All we have to do is inherit from
LoggerMixin and we can proceed using
self.logger as if we'd set that up in
BusinessLogicer! Pretty awesome.
There are certain places where heavy use of mixins can save a ton of time or cognitive load. A use case that came up recently was in a Django project where several class-based views needed to serve only traffic within our intranet. An existing middleware does this check, raising an
Http404 if a request comes from outside the network. Adding that functionality to our classes was fairly easy, using
method_decorator on the views'
from django.utils.decorators import decorator_from_middleware, method_decorator from django.views import generic from app import middleware network_protected = decorator_from_middleware( middleware.NetworkProtectionMiddleware ) @method_decorator(network_protected, name='dispatch') class SecretView(generic.View): ...
However, when I added a second piece of functionality I had to decorate those views too. I was getting a little tired of decorating.
Instead, I refactored the network protection integration into a mixin, allowing all our class-based views to inherit from this as they saw fit:
from django.utils.decorators import decorator_from_middleware, method_decorator from django.views import generic network_protected = decorator_from_middleware( middleware.NetworkProtectionMiddleware ) class NetworkProtectionMixin: @method_decorator(network_protected) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) class SecretView(NetworkProtectionMixin, generic.View): ...
A similar approach can be used for Django's permissions, authentication, and more. This is powerful, and when used correctly can result in wonderfully expressive code. It also reduces complexity, allowing easier unit testing and comprehension.
At the end of the day, mixins are a DRY way to architect code, abstracting potentially complex functionality so that developers can concentrate more fully on the task at hand instead of propping up all the pieces. Rejoice!
Have you used mixins in practice? I'd love to hear more.