Implementing Custom Decorator Functions in Python (55/100 Days of Python)
In Python, a decorator is a special type of function that can be used to modify the behavior of another function. Decorators allow you to add functionality to an existing function without modifying its code directly. This can be useful for adding logging, authentication, or other types of functionality to a function without cluttering up its code.
Basics of Decorator Functions
A decorator function in Python is a higher-order function that takes a function as an argument and returns a new function. The new function can be used to modify the behavior of the original function. We can define a decorator function as follows:
def my_decorator(original_function):
def new_function():
# do something before the original function
original_function()
# do something after the original function
return new_function
To use a decorator function, you can apply it to an existing function by placing the decorator function name above the function definition:
@my_decorator
def original_function():
print('Hello, World!')
Now, when you call the original function, the decorator function will be executed before and after the original function:
original_function()
# do something before the original function
# Hello, World! -> the original function
# do something after the original function
Real-World Use Cases of Decorator Functions
Decorators are a powerful feature in Python and can be used for a variety of tasks. Some of the most common use cases for decorators include:
- Logging: You can use a decorator to log information about function calls, such as the arguments passed to the function and the return value.
- Caching: You can use a decorator to cache the results of a function, so that if the function is called again with the same arguments, the cached result is returned instead of re-computing the result.
- Authentication: You can use a decorator to require authentication before a function can be called, ensuring that only authorized users have access to the function.
- Timing: You can use a decorator to measure the time it takes for a function to execute, which can be useful for optimizing performance.
Creating a Custom Decorator in Python
Creating a custom decorator in Python is easy. You can define a function that takes a function as an argument and returns a new function that modifies the behavior of the original function. Here’s an example of a custom decorator that logs information about a function call:
import logging
def log_function_call(original_function):
def new_function(*args, **kwargs):
logging.info(f'Calling function {original_function.__name__} with args={args} kwargs={kwargs}')
result = original_function(*args, **kwargs)
logging.info(f'Function {original_function.__name__} returned {result}')
return result
return new_function
In this example, the log_function_call
decorator takes a function as an argument and returns a new function that logs information about the function call. The *args
and **kwargs
parameters allow the decorator to accept any number of positional and keyword arguments. To use this decorator, you simply apply it to an existing function:
@log_function_call
def my_function(x, y):
return x + y
Now, when you call the my_function
function, the log_function_call
decorator will log information about the function call:
my_function(1, 2)
# Calling function my_function with args=(1, 2) kwargs={}
# Function my_function returned 3
Decorators With Arguments
In Python, decorators can also accept arguments. These types of decorators are called “decorator factories” because they return a decorator function that can be used to modify the behavior of another function with specific arguments.
To create a decorator factory, you define a function that takes arguments and returns a decorator function. The decorator function can then be used to modify the behavior of another function. Here’s an example of a decorator factory that takes an argument n
and returns a decorator function that multiplies the result of the decorated function by n
:
def multiply_result_by(n):
def decorator(original_function):
def new_function(*args, **kwargs):
result = original_function(*args, **kwargs)
return result * n
return new_function
return decorator
In this example, multiply_result_by
is a decorator factory that takes an argument n
. The returned decorator function, decorator
, takes a function as an argument and returns a new function that multiplies the result of the decorated function by n
.
To use this decorator factory, you can apply it to a function and pass the argument n
:
@multiply_result_by(2)
def my_function(x, y):
return x + y
Now, when you call the my_function
function, the result is multiplied by 2:
my_function(1, 2) # 6
You can also use multiple decorator factories to apply multiple decorators to a function with different arguments:
@multiply_result_by(2)
@log_function_call
def my_function(x, y):
return x + y
In this example, the multiply_result_by
decorator factory multiplies the result of my_function
by 2, and the log_function_call
decorator logs information about the function call.
Notice how the function multiply_result_by
returns a decorator. Which then wraps the function passed to it through @
and extends the functionality.
It’s also important to keep in mind that the order of the decorators can matter in some cases. For instance, in the example above, if we swap the places of @multiply_result_by
and @log_function_call
, we would obtain different results:
@multiply_result_by(2)
@log_function_call
def my_function(x, y):
return x + y
my_function(1, 2)
# Calling function my_function with args=(1, 2) kwargs={}
# Function my_function returned 3
@log_function_call
@multiply_result_by(2)
def my_function(x, y):
return x + y
my_function(1, 2)
# Calling function my_function with args=(1, 2) kwargs={}
# Function my_function returned 6
This happens because Python executes the functions in the given order. So, if we first decorate our function with @multiply_result_by
, and then @log_function_call
, it will execute the function, then log the result, and then multiply the result. Yet, if we swap the order of logging and multiplying, then Python will first execute the function, then multiply the resulting number, and then log the result.
What’s next?
- If you found this story valuable, please consider clapping multiple times (this really helps a lot!)
- Hands-on Practice: Free Python Course
- Full series: 100 Days of Python
- Previous topic: Static Methods
- Next topic: Class Decorators in Python