Originally described in the “Design Patterns: Elements of Reusable
Object-Oriented Software” book, decorators are a powerful design pattern that
allows us to extend or augment an object’s functionality. In this post I will
show with some practical examples how this can be achieved in Python via the
What do decorators do?
The important question is not what decorators are, but what they can do
for us. Decorators can be implemented as functions or as classes (we will see
examples of both later), but what they do is “wrap” functions (or even classes)
so that their behavior can be changed seamlessly. Python provides the
@decorator syntax to do this, though it is merely syntactic sugar and not
actually required to implement the decorator pattern.
Before we start implementing decorators though, there are a few things we need to remember:
- Functions and classes are objects, and they can be passed around as arguments, functions can also return functions.
- When we define nested functions, they will have access to the context they were defined in. This is basically a closure.
Building the concept with examples
A decorator will wrap a function and add some behavior (before or after) the original function. Probably the simplest way of decorating a function is:
This works, but it’s pretty clunky. There’s the obvious problem that we need to
remember to call the
decorated function instead of the original. Of course we
could add something like
say_name = decorated at the end of our code, but
there’s also another problem, our “decorator” is pretty much impossible to
reuse. It’s not that this way of decorating functions doesn’t work, it does,
but it’s just not particularly useful.
If we consider we can pass functions around, we can come up with a slightly more reusable way of decorating functions. We can write our decorator as a function that takes a function to be wrapped and whose return value will be the decorated function. This sounds like a bit of a tongue-twister, it is better understood when seen in action.
If we now call
wrapped_say_name we will get:
I'm going to call the original function... I'm Mauro. I just called the original function.
There’s a few important things to note here:
- We are passing the
say_namefunction as an argument to
- The nested function,
wrapped, has access to the context in which it was defined and can call
- The return value of our function (
decorator) is a function (
This is clearly much better as we can now reuse the same code to wrap other functions. This is still, however, a very flawed example as there are a couple of things we are not considering:
- What would happen if the wrapped function had arguments that needed to be passed?
- What would happen if the wrapped function had to return a value?
Addressing the second point is the easiest, we just need to add a
Addressing the first point might be a bit tougher if we want to make sure our
decorator is as generic as it can be. We can obviously add the same parameters
function_to_wrap would take (if it took any), but the generic solution
here would be to take advantage of using
* and **
in function definitions.
Let’s look at a more real life example of decorating a function. Let’s create a decorator that will time how long a function takes to execute and will print it out.
The output was:
Function took 1.9073486328125e-06 seconds. 0.5 Function took 9.5367431640625e-07 seconds. 5.0
In this case we wrapped the
division function and timed how long it took.
This is a more interesting example because
division takes two arguments, the
numbers to divide, and also returns the result. What the interface will look
like for our decorator, and what we will do with the return value is something
that we need to account for when designing a decorator. In this case the
decorator’s interface is as generic as it can be, basically taking any
positional and keyword arguments and passing them on to the wrapped function.
It also has to be slightly careful with the return value, as it needs to store
it before returning it so that it can print out the “Function took…” message
instead of returning prematurely.
There is one thing this decorator is still not considering though, exceptions.
Some functions, like the division function can raise exceptions. It is then the
decorator’s responsibility to decide that to do (or not do) with them. In this
case exceptions are ignored and allowed to go though, but this means we will
never know how long it took for the function to fail. If it was relevant to us
we would probably wrap the call in a
try/except block and re-raise the
A fixed version of our decorator could look like:
Something interesting about exceptions raised in decorators is that when looking at the stack trace we can see the decorator is there.
Now that we’ve understood what decorators can do, let’s look at some syntactic sugar we can use and at some more complex examples.
Python provides a handy way of decorating functions by preceding a function
definition with a
@ followed by the decorator name. This can be used
in module-level functions or even in class methods. If we go back to our
previous example, we can simply define our
division function as:
If we do this, our definition of
division is replaced by the decorated
function, so in this case using
division with what we
previously defined as
@timed just allowed us to call
the wrapped function as if we were calling the original one.
Decorators with parameters
Up until now, the decorator we were using did not take any arguments, but what
if it did? In this case, the function to wrap would be wrapped with the result
decorator(args). This means that our decorator function has to be
first “built” before it can be used to return a decorated function. This sounds
a bit confusing so let’s look at an example. Let’s take the
from before and make it so that it can print a shorter version of the message.
For brevity’s sake we will remove the exception handling. This new decorator
will look like so:
This will output:
Function call took 2.1457672119140625e-06 seconds. 0.5 1.1920928955078125e-06s. 0.5
timed function now does is build the decorator itself, while the
decorator function is what actually decorates
decorator is now
timed was in our previous examples.
It is important to note that now when decorating something, we need to decorate
with a call. Using plain old
@timed will not work, we need to either use it
@timed() or pass a boolean in this case like
To do this we could also use a class instead of nesting functions. Basically instead of returning a function we can return a class instance that is also callable. Calling that class instance will return the decorated function. An alternative implementation of the decorator in this case could look like so:
When we do
@timed(), a new instance of the
timed class is created and
verbose = false. Then that instance is called with
division as an argument which is stored in
self.decorated_f and a reference
wrapper method is returned. It is the
wrapper method that will
division and replace it. Note that I opted to make the
method, but it could have also been a nested function like we had been doing
Looking back, we could have also implemented the original decorator as a class
__init__ that took the function to wrap and a
__call__ to act as
the decorated function.
If you want to use a class or a set of nested functions is really up to you, it depends on what you want to implement and what would look best in that case.
So far we’ve seen that it is rather easy to decorate a function and replace the original with it, but by replacing the original we are actually destroying some information.
All functions have a
__name__ attribute which returns, you’ve guessed it,
its name. Functions will also have a
__doc__ attribute with their docstring.
However, by replacing the original function, these are lost. If we took
our last example and checked the value of
division.__name__ we would see it
'wrapper'. If we checked
division.__doc__, the wrapper’s docstring
would be returned and not the one from our
division function (if we had one).
Examples in the standard library
Some commonly used decorators in the standard library include:
@classmethodfor transforming a method into a static method and a class method respectively.
@propertyfor defining managed attributes.
@contextlib.contextmanagerfor easily turning a function into a context manager.
unittest.mock.patchfor easily patching functions during unit tests.
functools.lru_cachefor caching method calls. The implementation if the latter is actually quite cool.
It is also possible to decorate classes, the difference is that the decorator will return a class instead of a function (or at least something that behaves like a class).
One example of this is the
@dataclass decorator in the
While this can be useful, it means treading deeper into metaprogramming territory, and depending on the case, it is possible that metaclasses fit this use-case better. This is a whole other topic and is something too big to cover here.
Decorators are powerful tools that allow us to enhance our code and change its behavior in a very simple way that does not require us to change the structure of our programs. Mastering the use of decorators is a must for every Python programmer.