Python Tips: Decorators
In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.
— Design Patterns
In Python, you can use a function or class to implement decorators to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.
The scenarios for using decorators include loggings, performance testing, transaction processing, caching, permission verification, etc. Decorators are an excellent design to solve such problems.
Through a simple example, to explain why we want to use decorators in our codes. First, we have a function that can print some messages.
Now, there is a new requirement to save the execution log of the function, we can add
print('Call foo()') to the function.
If the other functions have similar requirements, how do we do it? Copy the log code to the other functions? This results in a lot of similar codes. In order to reduce redundant code, we can redefine a new function: specifically process the log, and execute the real business code after the log.
Although the above code meets our requirements, this solution breaks the structure of the code, call the logging code instead of the business code
foo() where it should be called. To handle this problem in a better way, we can use decorators.
This is our solution with the decorator in Python.
log_decorator is a decorator that prints the execution log of the function. To implement the decorator, we use four features of python functions:
def log_decorator(func):: Functions can be passed around and used as arguments.
def log_wrapper(*args, **kwargs): Inner functions
return log_wrapper: Returning functions from functions
- The function can be assigned to a variable.
@log_decoratoris a syntactic sugar equivalent to
foo = log_decorator(foo).
functools module is for higher-order functions: functions that act on or return other functions.
@functools.wraps(func) is also a decorator that will preserve information about the original function.
@functools.wraps(func) before the definition your
def log_wrapper(*args, **kwargs) use
*kwargs, it will accept an arbitrary number of positional and keyword arguments.
Further, if we need the decorator with some arguments, we need to write a higher-order function that returns a decorator. Please see the code below.
This is a triple nested decorator. It is easier to understand this program if you do not use syntactic sugar.
First, the return value of the
log(message) function is a decorator function
log_decorator(func) , and the return value of the
log_decorator(func) is wrapper function
The program starts with the following line:
foo = log('Call')(foo)
In effect, the program executes
log('Call'), which returns the decorator function
log_decorator(func), and then calls the returned function with the parameter
foo() function and the return value is eventually the wrapper function.
Python Decorators in Practices
Decorators are often used in projects for login verification, permission checking and other scenarios. In Django source code, uses two decorators
@permission_required to check the user’s log-in and permission.
The implementation of
user_pass_test shows that it uses the decorator. In your code, you just need to add
@permission_required before your process to complete the login and permission control.
In practice, if you suspect that some functions are running too long, increase the system latency. So you want to test the execution time of certain functions onsite, then decorators are a very common way to do this. Decorators are non-intrusive to project code.
The following is a simple demonstration:
log print the runtime of a function and return the result of its execution. If you want to calculate the execution time of any function. just add
@log above the function.
The decorator’s feature of reducing repetitive code can also be used to help us compute the Fibonacci sequence.
We use a decorator to save the result of each
fib() calculation in a dictionary
cache. This solution is more efficient than using recursive ideas.
What’s new in Python 3.9 decorators
Thanks for reading.
Stay Healthy, Stay Coding.