Python is praised for its clarity and syntactic sugariness. In this article, I will teach you to use decorators in Python to make your code readable and clean.
What are decorators?
To understand what decorators are, you first need to be familiar with the way Python handles functions. From its point of view, functions are no different than regular objects: they have properties and can be reassigned:
def func(): print('hello from func') func() # hello from func new_func = func new_func() # hello from func print(new_func.__name__) # func
Moreover, you can pass them as arguments to other functions:
def func(): print('hello from func') def call_func_twice(callback): callback() callback() call_func_twice(func) > hello from func > hello from func
Now, to decorators. A decorator is used to modify the behaviour of a function or a class. The way this is achieved is by defining a function (decorator) that returns another function. This sounds complicated, but you will understand everything with this example:
Let’s go step-by step. Firstly, we define the
logging_decorator function on line 1. It accepts a single argument, which is the function we are trying to decorate. Inside, we define another function, the
logging_wrapper is then returned and is used in place of the original decorated function. On line 7 you can see how the decorator is applied to the
sum function. Later, on line 11, when we call
sum, it will not just call
sum: it will call the
logging_wrapper, which will log before and after calling the
sum. Let’s go through some more examples now.
Why do you need decorators?
It is simple: readability. Python is praised for its clear and concise syntax, and decorators are no exceptions. If there is any behaviour that is common to more than one function, you probably need to make a decorator. Here are some examples when they might come in handy:
- Check arguments type at runtime
- Benchmark function calls
- Cache function results
- Count function calls
- Check metadata (permissions, roles, etc.)
- And much more…
Now I will list some code examples:
Decorators with return values
Suppose we want to know how long each function call takes. Also, functions return something most of the time, so the decorator must handle that as well:
You can see we store the returned value in
result on line 5. But before returning it, we have to finish timing the function. This is an example of behaviour that would not be possible without decorators.
Decorators with arguments
Sometimes we want a decorator which accepts values (like
@app.route('/login') in Flask):
You can see that in order to achieve it, we defined an extra function that accepts an argument and returns a decorator.
Decorating with classes
It is possible to decorate using classes instead of functions. The only difference is the syntax, so you do what you are more comfortable with. Here is the logging decorator rewritten using classes:
The upside is that you do not have to deal with nested functions. All you need to do is define a class and override the
There may be times when you want to decorate each and every method in a class. You could always write it like this:
class MyClass: @decorator def func1(self): pass @decorator def func2(self): pass
But if you have lots of methods, this can get out of hand. Thankfully, there is a way to decorate the whole class at once:
Now, do not panic. This looks complicated, but this is the same logic. Firstly, we leave the
logging_decorator as is: it will be applied to all methods of a class. Then we define a new decorator
log_all_class_methods. It is like a regular decorator, but returns a class instead. The
NewCls, has a custom
__getattribute__: for all calls to the original class, it will decorate the functions with the
Not only can you define your own decorators, but there are some shipped in the standard library as well. I will list 3 I have worked most with:
@property– a decorator from builtins, which lets you define getters and setters for class properties
@lru_cache– a decorator from the
functoolsmodule. It memorizes funcion arguments and return values, handy for pure functions, like the
@abstractmethod– a decorator from the
abcmodule. Indicates that the method is abstract and implementation details are missing.
Thank you for reading, I hope you liked my article. Stay subscribed for more Python content!
- PEP 318 — Decorators for Functions and Methods
- Higher-order functions and operations on callable objects