Let’s face it: you can code just fine without decorators. In fact, you can even write good code without them. However, decorators are an awesome feature of all modern languages – and for a reason. They allow you to write better, cleaner code. Maybe you have heard about them or even used some existing decorators without truly understanding them. You are in the right place since this Python Decorators Tutorial will cover it all! We will see what are decorators in python, how to write them, and use them. Soon, you will be a master of them!
What is a Python Decorator?
Believe it or not, a decorator has the sole purpose of decorating a function. In other words, it adds some functionalities to that function, altering its behavior. The funny part is that you apply the decorator to the function, and you do not alter its internal code directly.
A decorator decorates a function: it alters its behaviour.
If you program with classes in Python (as you should), you know about the decorator staticmethod
. You apply it to a method of your class, and it does not require the self
parameter anymore.
@staticmethod
def do_something_statically(a, b)
pass
This will alter our function, without messing with its internal code. Later in this Python Decorators Tutorial, we will see many applications of decorators.
What is a Python Decorator, exactly?
Okay, now we know what a decorator does, yet we have no definition for it. The truth is, a decorator is nothing more than a function.
A decorator is a function.
Specifically, a decorator is a function that, as a parameter, wants a function to modify. Imagine you have a function that receives a number and returns its double, like the one below.
def double(x)
return x*2
This function is altering the number x. For a decorator, x
will be a function – not a number. Like this function is returning another number, the decorator will return another function. If you think this is messy, as soon as you will see you will understand the beauty of decorators.
How to apply decorators
Before we dive into our Python Decorators Tutorial, we may want to know how to decorate a function. Well, you saw that from the first example: you write the decorator name above the function, after a @
sign.
@decorator
def function_to_decorate(a)
pass
Your decorator can accept parameters as well! Generally, you provide it with static info, as you want to alter this specific function always in the same way. By providing parameters statically for the decorator, you can do more than that. In fact, you also write such parameters close to the method, so you can see the whole picture in the same piece of code. Simply provide parameters as you would for a normal function.
@decorator('parameter')
def function_to_decorate(a)
pass
Order matters
Python applied decorators bottom up. In other words, it applies the decorator closest to the function first. Imagine you have three decorators as in the example below.
@a
@b
@c
def func(something):
pass
In this example, c
will decorate the function as usual. Then, b
will decorate the function as c
decorated it. And finally, a
will take the function as it comes out b
‘s manipulations and alter that as well.
Writing decorators in Python
Our first, simple, python decorator
Our first decorator is actually quite useful: it prints to screen the time it took for a function to run, using the time library. To do that, our decorator must alter the target function and create a new function that does the following.
- Measures what time it is
- Runs the code from the original function and store the result
- Measure what time it is and subtract the first measurement, obtaining the difference and printing it
- Returns the stored result
As you can see, we are adding stuff before and after the original code. We are going to call this decorator measure. Here is what it looks like.
import time
def measure(func):
def func_wrapper():
start = time.time()
result = func()
print(time.time()-start)
return result
return func_wrapper
The explanation
As you can see, we define a function inside the decorator. This function, func_wrapper
, is the altered function after the decoration. As you can see from the last line, we are returning this function from the measure
decorator. Note that in the return statement we didn’t use the brackets, and for a reason. By using the brackets, you would call the function and return what that function returns. Instead, we want to return the function itself.
The func_wrapper
function is way more simple. It measures the start time, it runs the original function func
and prints the time elapsed before returning the results.
From problem to solution…
If you look closely at the script above you will see a major problem. We call the original function with no parameter at all. This means that we can only decorate functions that do not accept parameters, that is useless. Our Python Decorators Tutorial wants to get you up and running with the real stuff, so we have two options.
The first thing you may think about is adding the exact parameters of the function you want to decorate. This will work, perfectly. However, you will be able to decorate only that function. Furthermore, if the function changes its parameters you will have to change the decorator as well. Clearly not the way to go.
A much better option is using args
and kwargs
. They represent positional and named parameters. Not only we need to call the original function with them, but we also want our wrapper to accept them. After all, the manipulation of the function must return a function that accepts those parameters! So here you are.
def measure(func):
def func_wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(time.time()-start)
return result
return func_wrapper
Now we can apply this decorator to everything, from standalone functions to class methods.
Decorators with Parameters
As we saw at the beginning of the article, we can pass some parameters to the decorator itself. There are several reasons you may need to do that, and a common example is wrappers. Imagine you want to enclose the output of your function between something, like HTML tags. You may want to have a decorator “tag” which applies a tag of your choice.
This is the point where you realize you are in Inception, the 2010 movie. In fact, you can’t add the parameters to your decorator unless you wrap it in another function that accepts the parameter. Let me state this in another way: your decorator now returns another decorator, which returns an altered function. That’s what I call nesting!
def tag(tag_name):
def tag_decorator(func):
def func_wrapper(*args, **kwargs):
return "<{0}>{1}".format(text, func(*args, **kwargs))
return func_wrapper
return tag_decorator
Now, you can call this decorator in the following way.
@tag('h1')
def func(something):
pass
Getting creative with Decorators
After this Python Decorators tutorial, you have everything you need to know to work with decorators. However, approach them in a creative way. Decorators allow you to add extra features to your function without altering the function itself. At first, this might sound silly, but it isn’t. It allows you to make every function stick with its purpose, and add the extra stuff outside of it.
You don’t have to call the original function once in the decorator. You may call it twice, or even more – or, you may not call it at all. For example, you could create a decorator that tries a function in a while True
loop until something happens. Even better, you could create a decorator that create a thread or process consumer that runs your function asynchronously. No matter your goal, just be creative and decorators will help you!
Summing it up (TL;DR)
In this Python Decorators Tutorial, we explained all the beauty of decorators. You can now use them to make your code cleaner and better in general. For those of you in a hurry, here you have the key points.
- Apply a decorator by adding this syntax just above the function you want to decorate:
@decorator
- The decorator may accept parameters if that’s the case just go with
@decorator('1', 'blah')
- You may apply multiple decorators, one per line. However, Python will apply them in bottom-up order.
- To define a decorator, you need to write a function that accepts a function as a single parameter. Inside that function, create another function: this is the altered function, and your decorator should return just that. This internal function, the “wrapper” may call the original function and perform additional operations
What brought you to decorators? How are you planning to use them? Let me know what you think in the comments, and I’ll be glad to answer!