Python Decorators: When to use and when to avoid them
2 min read

Python Decorators: When to use and when to avoid them

Python Decorators: When to use and when to avoid them
Photo by Spacejoy / Unsplash

Decorators are used in Python to enhance or modify the way functions and classes behave. Essentially they are used to add more functionalities to existing functions without modifying the functions' source codes. To define a decorator, you simply append the symbol @ before your decorator function name.

A decorator is also defined as a function that accepts another function as an argument and return a new function. In the example below, a decorator function called greeting takes a function func as an argument. Within the greeting, an inner function called wrapper containing the additional functionality we want to add is defined. In this case, a message is printed before and after the original function is called.

The decorator function greeting returns the wrapper function replacing the original function with the decorated form. To apply the decorator, we place the @greeting syntax on top of welcome function.


def greeting(func):
    def wrapper():
        print("Hi all!")
        func()
        print("Let's have some fun.")
    return wrapper

@greeting
def welcome():
    print("Welcome you all to Decorator's party!")

welcome()


Hi all!
Welcome you all to Decorator's party!
Let's have some fun.

To further enhance our function, we can pass arguments to the original as well as the decorator function as follows:


def greeting(func):
    def wrapper(arg: str):
        print(f"Before we start, let's introduce {arg}.")
        func(arg)
        print("Let's have some fun.")
    return wrapper

@greeting
def welcome(arg: str):
    print(f"Hi {arg}. You are welcome to Decorator's party!")

welcome("Bob")


Before we start, let's introduce Bob.
Hi Bob. You are welcome to Decorator's party!
Let's have some fun.

When to use Decorators

  • When you want you add common functionality to multiple functions without duplicating codes. For example logging, input validation, caching, etc.
  • When you want to modify a function or a class behaviour without changing the original source code of the function.
  • When you want to properly organize your code and improve maintainability by separating concerns such as authorization from the core logic of your function or class.

Common use cases for Decorators

  • Logging - Decorators are commonly used to log functions' calls and return their values.

def log_function_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function {func.__name__} with arguments {args}")
        result = func(*args, **kwargs)
        print(f"Function {func.__name__} call returned {result}")
        return result
    return wrapper


@log_function_call
def compute_circle_area(radius: float, pi: float) -> float:
    return radius * radius * pi


compute_circle_area(6.5, 3.142)


Calling function compute_circle_area with arguments (6.5, 3.142)
Function compute_circle_area call returned 132.74949999999998

  • Input Validation - Decorators can be used to validate the expected argument type when a function is called.

def validate_function_input(func):
    def wrapper(x):
        if not isinstance(x, str):
            raise ValueError("Function argument must be a string")
        result = func(x)
        return result
    return wrapper


@validate_function_input
def get_name(x):
    return x


get_name("Bob")

  • Authorization - Decorators can also be used to verify if a user is authorized to access a function.

def grant_access(usertype):
    def access_decorator(func):
        def wrapper(*args, **kwargs):
            if usertype != "admin":
                raise PermissionError(f"{usertype} is not permitted to access this function")
            else:
                result = func(*args, **kwargs)
                return result
        return wrapper
    return access_decorator


@grant_access("admin")
def add_user(user_id):
    print(f"Adding user {user_id}")


add_user(6784)

When to avoid Decorators

  • Excessive use of decorators can make code complex to maintain. Avoid decorators where simple explicit function calls are sufficient.
  • When decorators impact on code performance is evaluated to be significant, consider alternative approaches.
  • Avoid decorators if it is determined that their usage can introduce tight coupling (binding) between decorators and decorated functions or classes.