Improve Your Code with Decorators
How to create your own decorators in Python
In this article, we will see how powerful decorators are. If you are new to Python (and maybe even if you are not), you probably have never heard of this construct. It is something specific to Python, which allows defining some general behavior that is common to many methods in a separate function.
For example, suppose that we have the following methods:
def sum(a, b):
return a+bdef subtract(a, b):
return a-b
Now we want to print the result to the console, before returning it. What should we do? The first idea is to just add a print statement inside all functions. But what if then we decide to save it in a file instead? WE would need to change all the functions again.
Instead, we can create a decorator log_result
and add it to the methods:
@log_result
def sum(a, b):
return a+b@log_result
def subtract(a, b):
return a-b
And now if we want to change where we write the output, we just need to change the code inside log_result
.
What is a decorator?
Now we have seen how decorators can be used, but what is a decorator exactly?
To give you a formal definition¹,
A decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.
Let’s break down this piece by piece.
First of all, a decorator is a function that takes another function as parameter: this means that we will define the log_result
decorator as:
def log_result(func):
Then we need to modify this function and return the modified version.
So we can create an inner function (which I called inner
for simplicity) which execute func
, print out the result and finally return this value:
def log_result(func):
def inner():
res = func()
print(res)
return res
Finally, the decorator must return the modified function:
def log_result(func):
def inner():
res = func()
print("The result is ", res)
return res
return inner
We have created our first decorator! Well, not really. It works only if the function we add it to has no parameters. For example:
@log_result
def get_even_digits():
return [0, 2, 4, 6, 8]
When we call this function the log will print on the screen:
>>> l = get_even_digits()
The result is [0, 2, 4, 6, 8]
However, if we try to use it with the sum
and subtract
methods from before, the code will not run correctly.
The problem is that we haven’t given the modified function the ability to receive the parameters that should be passed to func
. Luckily, this can be resolved easily. We will redefine inner
as follows:
def log_result(func):
def inner(*args, **kwargs):
res = func(*args, **kwargs)
print("The result is ", res)
return res
return inner
Note: if you don’t know what *args
and **kwargs
are, check this article.
Now inner
can get any number of parameters, and pass them directly to the function. We have done it!
>>>s = sum(3, 5)
The result is 8
>>>d = subtract(8,5)
The result is 3
Decorators with parameters
Lastly, we may want to pass some additional parameters to the decorator. For example, we may want to print also a name together with the result, so that we know from which operation this log is coming from.
To do so, we need to modify slightly the function:
def log_with_name(name):
def log_result(f):
def inner(*args, **kwargs):
res = f(*args, **kwargs)
print(name + ": " + str(res))
return inner
return log_result
Let’s see what’s happening. The log_with_name
function takes the name string and returns the decorator which will then be applied to a function.
It can be used exactly as before:
@log_with_name("Sum")
def sum(a, b):
return a+b@log_with_name("Difference")
def subtract(a, b):
return a-b
And the result will be:
>>> s = sum(3, 5)
Sum: 8
>>> d = subtract(8, 3)
Difference: 5
Conclusion
Thank you for reading through the end! Here are some other resources on decorators:
More content at plainenglish.io. Sign up for our free weekly newsletter. Get exclusive access to writing opportunities and advice in our community Discord.