Python > Advanced Topics and Specializations > Meta-programming > Decorators

Decorator with Arguments (Decorator Factory)

This example demonstrates a decorator factory. This is a decorator that takes arguments and returns a decorator. This is useful when you need to configure the behavior of your decorator.

Concepts Behind Decorator Factories

A decorator factory is a function that returns a decorator. This allows you to pass arguments to the decorator. This is useful when you need to configure the behavior of your decorator based on some parameters. The decorator factory pattern is used to create parameterized decorators.

The Logging Decorator Factory

The log function is our decorator factory. It takes a message as input. Inside log, we define a decorator function that takes the original function func as input. Inside decorator, we define a wrapper function that will be executed when the decorated function is called. The wrapper prints a log message before and after calling the original function func. Finally, the decorator function returns the wrapper function, and the log function returns the decorator. The @log('DEBUG') syntax above add is equivalent to writing add = log('DEBUG')(add).

import functools

def log(message):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f'{message}: Calling {func.__name__} with args {args} and kwargs {kwargs}')
            result = func(*args, **kwargs)
            print(f'{message}: {func.__name__} returned {result}')
            return result
        return wrapper
    return decorator

@log('DEBUG')
def add(x, y):
    return x + y

result = add(5, 3)
print(f'Result: {result}')

Real-Life Use Case

Decorator factories are used when the decorator needs to be configured with arguments. This is common in logging, where you might want to specify the log level, or in caching, where you might want to specify the cache expiration time. Another use case is access control, where the role required to call a function can be passed as an argument to the decorator.

Best Practices

  • Use functools.wraps to preserve the original function's metadata.
  • Ensure that the decorator factory returns a proper decorator function.
  • Consider using type hints for clarity.

Interview Tip

Be prepared to explain the difference between a decorator and a decorator factory. Understand how the arguments are passed to the decorator factory and how the returned decorator is applied to the function. Also, be able to write a simple decorator factory from scratch.

When to Use Them

Use decorator factories when you need to parameterize the behavior of your decorators. This provides greater flexibility and allows you to reuse the same decorator with different configurations.

Memory Footprint

Decorator factories have a similar memory footprint to regular decorators. The overhead is minimal and is often outweighed by the benefits of code reuse and flexibility.

Alternatives

Alternatives to decorator factories include using class-based decorators or creating separate decorators for each configuration. However, decorator factories are often the most concise and elegant solution.

Pros

  • Flexibility in configuring decorator behavior.
  • Code reuse and reduced duplication.
  • Clean syntax.

Cons

  • Can be more complex to understand than simple decorators.
  • May require more code to implement.

FAQ

  • Can I use multiple decorators on a single function?

    Yes, you can apply multiple decorators to a single function. The decorators are applied from top to bottom, with the topmost decorator being applied first.
  • How do I access the arguments passed to the original function inside the decorator?

    The arguments passed to the original function are passed to the wrapper function. You can access them using *args and **kwargs.