Decorators in Python are a special kind of function that modify or extend the behavior of another function. They are a design pattern used to add extra functionality to a function without having to modify its code directly.
How do decorators work?
Example 1
Say we have a function that prints out Hello, world! when it’s called:
def say_hello():
print("Hello, world!")
Now, let’s say we want to add some extra functionality to this function, like printing a message before and after it’s called.
We can do this by modifying the function directly, but that could get messy. If we have a lot of functions that need this extra functionality we would have to manually add it everytime.
This is where decorators are used.
A decorator takes our main function as an argument and then uses an inner wrapper function to modify it.
For example:
def my_decorator(func):
def wrapper():
print("Before the function is called.")
func()
print("After the function is called.")
return wrapper
In the above example:
- We define a decorator function,
my_decorator
.
my_decorator
function takes another function,func
as an argument.
- The decorator function then defines an inner function called wrapper that adds some extra functionality before and after calling the original function. In this case, two
print
statements are added before and after thefunc
statement.
We use the @
symbol followed by the name of the decorator to call a decorator function. In this case, we apply the my_decorator
function to the say_hello
function using @my_decorator
.
The modified say_hello
now looks like this:
@my_decorator
def say_hello():
print("Hello, world!")
say_hello()
Now when the say_hello
function is called the output is :
Before the function is called.
Hello world.
After the function is called.
This occurs because the say_hello
function calls the wrapper()
function defined inside the decorator function, which adds the extra functionality of adding the two print
statements.
Example 2
Now we have a function multiply
. multiply
multiplies two numbers and returns the result:
def multiply(a, b):
return a * b
We want to modify this function to check if both numbers are positive before executing the multiplication.
We can use a decorator to add this functionality without modifying the original function:
def check_positive_numbers(func):
def wrapper(a, b):
if a > 0 and b > 0:
result = func(a, b)
return result
else:
print("Both numbers must be positive")
return wrapper
The decorator check_positive_numbers
has a wrapper
function inside it. This wrapper
function takes two arguments a
and b
and checks if both a
and b
> 0.
If they are, the original function multiply
(with arguments a
and b
) is called and the result is returned.
If not, a message is printed saying that both numbers must be positive.
The decorator can now modify our multiply function like this:
@check_positive_numbers
def multiply(a, b):
return a * b
This tells Python to use the check_positive_numbers
decorator to modify the multiply function. Now, when multiply()
is called, it first checks if both numbers are positive before executing the multiplication.
>>> multiply(3, 4)
12
>>> multiply(-3, 4)
Both numbers must be positive
>>> multiply(3, -4)
Both numbers must be positive
Different ways to use decorators.
Decorators in python are really helpful in keeping your code clean, efficent and organized. Decorators are used in different ways::
- Logging: A decorator logs the input and output of a function to a file or console.
- Timing: It is used to time the execution of a function and log the execution time.
- Validation: Decorators validate the inputs of a function and raise an exception if the inputs are not valid.
- Caching: A decorator is also used to cache the output of a function for a certain period of time, so that the same inputs don’t have to be re-computed every time the function is called.
- Authentication: Another very vital use of a decorator function is to check if a user is authenticated before allowing them to access certain pages or perform certain actions. Different webframeworks, like django, use decorators to authenticate users.
- Error handling: A decorator is used to catch and handle exceptions raised by a function, and log the error message.
Overall, decorators are a powerful tool in Python and can help you write more modular, maintainable, and reusable code.