Python Tips: Decorators

A very powerful and useful tool in Python

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.

Our Goal

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.

Decorator Solution

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:

  1. def log_decorator(func):: Functions can be passed around and used as arguments.
  2. def log_wrapper(*args, **kwargs): Inner functions
  3. return log_wrapper: Returning functions from functions
  4. The function can be assigned to a variable. @log_decorator is a syntactic sugar equivalent to foo = log_decorator(foo).

The 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.

REMEMBER: Add @functools.wraps(func) before the definition yourwrapper().

def log_wrapper(*args, **kwargs) use *args and *kwargs, it will accept an arbitrary number of positional and keyword arguments.

Advanced Decorator

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 log_wrapper(*args, **kwargs).

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

Authentication

Decorators are often used in projects for login verification, permission checking and other scenarios. In Django source code, uses two decorators @login_required and @permission_required to check the user’s log-in and permission.

Django user_passes_test Source code

The implementation of user_pass_test shows that it uses the decorator. In your code, you just need to add @login_required and @permission_required before your process to complete the login and permission control.

Logging

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:

The decorator 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.

Fibonacci sequence

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

  • Any valid expression can now be used as a decorator. Previously, the grammar was much more restrictive. See PEP 614

Thanks for reading.

Stay Healthy, Stay Coding.

Software Engineer, Kaggle Competitions Expert

Get the Medium app