avatarLynn Kwong

Summary

This article provides a comprehensive guide to understanding and mastering the decorator in Python, including its definition, usage, and application in various scenarios.

Abstract

The article begins by explaining the concept of a decorator in Python, which is a function that takes another function as input, extends its behavior, and returns a new function as output. It then demonstrates how to create and use decorators, including the use of the magical @ symbol as syntactic sugar to make the operation easier. The article also covers more complex topics such as decorating functions with parameters, preserving the original function's information using the wraps decorator from the functools module, and creating a decorator factory. Finally, the article provides a practical example of a decorator that can be used to track the execution time of a function.

Bullet points

  • A decorator in Python is a function that takes another function as input, extends its behavior, and returns a new function as output.
  • Decorators can be used to decorate both functions and classes.
  • The magical @ symbol is syntactic sugar that makes the operation of decorating a function easier.
  • Decorators can be used to decorate functions with parameters.
  • The wraps decorator from the functools module can be used to preserve the original function's information.
  • A decorator factory is a function that returns a decorator, not the decorated function.
  • Decorators can be used to track the execution time of a function.
  • Decorators are commonly used in various frameworks such as Scrapy, Flask, Django, etc.
  • Understanding decorators can make your life easier as a developer.

Understand and Master the Decorator in Python

Let’s demystify the decorators in Python

In the most common form, a decorator is a function that takes another function as input, extends its behavior, and returns a new function as output. This is possible because, in Python, functions are first-class objects, which means they can be passed as arguments to functions and also be returned from functions, just as other types of objects such as string, int, or float.

It should be noted that a decorator can also be used to decorate a class. A common use case is to use a class decorator as a registry for the classes created

Photo by laura adai on Unsplash

Let’s first demonstrate how a decorator works. Once you understand it, you won’t use it as a black box anymore.

Let’s create a super simple function:

Then let’s create a simple decorator, which is just a function that takes another function as input, does some decoration, and returns the decorated function as output:

Then let’s decorate the original function func1 with this decorator:

>>> func1_decorated = decorator(func1)
In decorator.
>>> func1_decorated()
Inside wrapper.
Do something before the function is executed.
Inside func1.
Inside wrapper.
Do something after the function is executed.

Now let’s introduce the magical @ symbol, which is just the syntactic sugar that makes the above function operation much easier:

With the @ symbol, func2 is decorated in the same way as func1 and would have the same results as func1_decorated:

>>> func2()
Inside wrapper.
Do something before the function is executed.
Inside func2.
Inside wrapper.
Do something after the function is executed.

Now you should have a basic understanding of what a decorator is, how to create one and how to use it to decorate a function. Let’s introduce a little more complexity and see how to decorate a function with parameters.

Note that the positional and keyword arguments are added to the wrapper but not the decorator.

Let’s create a function that takes a positional and keyword argument, respectively, and decorate it with the decorator above:

Let’s see how it works:

>>> print(multi_10(1))
Inside wrapper.
Do something before the function is executed.
Inside the original function.
Inside wrapper.
Do something after the function is executed.
10
>>> print(multi_10(1, times=3))
Inside wrapper.
Do something before the function is executed.
Inside the original function.
Inside wrapper.
Do something after the function is executed.
1000

It shows that the function with positional and keyword arguments is decorated correctly.

Let’s now tackle a more hidden problem. If you check the __name__ property of the decorated function multi_10, you will see something strange:

>>> print(multi_10.__name__)
wrapper

Strang, isn’t it? The __name__ of function multi_10 is not multi_10, but wrapper, which is the name of the decorated function returned by the decorator. If we want to preserve the information of the original function, we need to use the wraps decorator from the functools module. Yes! We are using a decorator inside a decorator:

Now function multi_10 should have the correct name:

>>> print(multi_10.__name__)
multi_10

So far we have only introduced the decorator which is a function that returns a decorated function. Besides decorator, there is the so-called decorator factory which is a function that returns a decorator, but not the decorated function. This can be confusing, but you will soon understand it through a simple example:

Note the parameters of the factory, the decorator, and the wrapper, which can be easily confused.

We can use this decorator factory to decorate our function. Unlike a regular decorator, we need to call the decorator factory when it is used for decoration.

We can see the following output when the function is decorated by the decorator factory:

Inside the decoractor_factory.
Inside the decoractor.

It means that the decorator factory is called first which is followed by the decorator function. If we run this decorated function, we can see the logs are printed as well:

>>> multi_10(1)
In wrapper.
Do something before the function is executed.
Inside the original function.
In wrapper.
The function is logged after execution.
10

Note that for a decorator factory, even if no parameters are needed, we still need to call it with parentheses, otherwise it won’t work:

This, however, will work properly:

Now that we have learned about decorator and decorator factory, let’s create a useful one that can be handy in your daily development work. Actually, most of the time, a decorator factory is also called a decorator. Therefore, we won’t distinguish them further.

Let’s create a decorator which can be used to track the execution time of a function.

Let’s decorate the multi_10 function with this new decorator and see what we will get:

This function is super fast. If you have a dozen of functions in your code and don’t know which one is time-consuming, you can use this simple decorator to find out which one is the evil one.

In this article, the decorator and decorator factory are introduced step by step, which includes the definition and usage in different scenarios. Decorators are used very commonly in all kinds of frameworks such as Scrapy, Flask, Django, etc. It can make your life easier if you understand what they are, how they work, and how to use them. I hope you would enjoy this article.

Related articles:

Python
Decorators
Function
Magical
Decoration
Recommended from ReadMedium