Things to remember before understanding decorators
- Functions in python are just like any other objects, that means:
- It can be passed around
- Called within another function
- Can be returned from another function
Decorator
A decorator wraps a function and allows you to put other functional codes around it. In other words, it lets you change the functionality of any function and modify (decorate) it to your need.
Here is an example:
We updated the lets_party function to be executed only in a certain time frame.
# Importing datetime
from datetime import datetime
# Defining the decorator function
def my_decorator(func):
def wrapper():
if 16 <= datetime.now().hour < 22:
func()
else:
pass
return wrapper
# Defining our function
def lets_party():
print('Party is ON!')
# Decorating my function with the decorator
wanna_party = my_decorator(lets_party)
# Calling the decorated function
wanna_party()
Some explanation:
- We defined the function lets_party, which prints ‘Party is ON!’ every single time.
- We then defined our my_decorator function, inside which we define a wrapper function which has a condition, which is that if the current hour is between 16 and 22 then execute the function passed to my_decorator. Then we return the wrapper function.
- We later call our decorator function which lets_party function as an attribute and asign it to wanna_party. That means, wanna_party now is pointing to the wrapper function which has a certain condition.
- So now when we call wanna_party, it will print ‘Party is ON!’ depending on the provided condition.
The key concept here is that we are passing a function within another function and we are also wrapping that function with another function.
Don’t worry about the syntax, I wrote the code this way so that it’s easier to understand what’s happening behind the scene. We will now look at a better way to write our decorators.
Pythonic way of writing a decorator
@my_decorator
def let_party():
print('Party is ON!')
And to call that function you can simply call it by saying: lets_party()
Decorating functions with arguments and returning value
Let’s say we have a simple divide function that returns the division of two numbers
def divide(a, b):
return a/b
If we use the above function with “0” as the second argument, the function will throw a ZeroDivisionError. Currently, the function isn’t equipped to handle that. Let’s change that and decorate our function with some additional functionalities.
NOTE: Our “divide” function here takes in argument as well as returns something
# Defining our decorator
def smart_divide(func):
def wrapper(a, b):
print(f'I am dividing {a} by {b}')
if b == 0:
print('Ooops! cannot divide by 0')
return # Returns None
# We need to return the func back since our original function is expecting something to be returned
return func(a, b)
return wrapper
@smart_divide
def divide(a, b):
return (a/b)
solution_1 = divide(4,2)
print(solution_1)
solution_2 = divide(2, 0)
print(solution_2)
And here’s the output
# Output for solution_1
I am dividing 4 by 2
2.0
# Output for solution_2
I am dividing 2 by 0
Ooops! cannot divide by 0
None
In this manner, we can decorate a function that takes parameters and is returning something as well.
Decorators that work with any number of parameters
If you noticed, the wrapper function in the above example takes the same number of parameters as our main function. We could modify the decorator function to work with any number of parameters (given that our main function supports it).
def works_for_all(func):
def wrapper(*args, **kwargs):
print('I can decorate any function!')
return func(*args, **kwargs)
return wrapper
Hopefully, now you know when and how to use decorators in python.
I have recently started a website, the info you provide on this website has helped me tremendously. Thank you for all of your time & work. “There can be no real freedom without the freedom to fail.” by Erich Fromm.