ProgrammingPython Developer

What are Python decorators with arguments, how are they implemented, where is their application justified, and what nuances are important when writing your own decorators with parameters?

Pass interviews with Hintsage AI assistant

Answer

Background

Decorators are one of the most powerful tools in Python, allowing you to modify the behavior of functions or methods. Sometimes, it's not enough to just "wrap" a function but to configure the decorator using parameters (arguments). These cases arise in logging, time checks, access restrictions, etc.

The Issue

Regular decorators accept only one function that needs to be wrapped. When parameters need to be passed to the decorator itself, the syntax becomes more complex, often leading to errors, especially with nested functions and passing *args/**kwargs.

The Solution

A parameterized decorator is implemented through a higher-order function: first, the outer "decorating" function is called with arguments, which creates and returns the actual decorator, and the decorator then receives the function and returns the wrapper:

def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def greet(name): print(f"Hello, {name}!") greet("Python") # Output: Hello, Python! (3 times)

Key Features:

  • A parameterized decorator is always implemented using triple nesting of functions
  • The wrapper must return the result and correctly pass *args/**kwargs
  • Don’t forget about functools.wraps to preserve metadata

Tricky Questions

Why can't a parameterized decorator be implemented the same way as a regular decorator?

When you apply @decorator, Python passes the function as an argument to the decorator. If you add parentheses (@decorator()), Python first calls the function, and only its result is interpreted as a decorator.

def deco(func): # regular decorator: @deco def deco_with_args(arg): # decorator with argument: @deco_with_args(arg)

How are decorators with arguments fundamentally different from decorators without arguments at the call level?

Decorators without arguments receive a function as input, while decorators with arguments receive parameters rather than a function and return a decorator from themselves.

How to properly use functools.wraps and why is it necessary?

functools.wraps(func) in the wrapper preserves the name, documentation string, and other metadata of the original function; otherwise, all this information will be replaced in the wrapper, which hinders debugging and introspection.

import functools def deco_with_args(arg): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorator

Common Mistakes and Anti-Patterns

  • Forget the need for three nested functions, creating only two (or even one), resulting in a non-decorator
  • Fail to pass *args/**kwargs inside the wrapper
  • Lose metadata of the function due to the absence of functools.wraps

Real Life Example

Negative Case

A decorator was implemented without considering parameters or with the incorrect number of nested functions:

def log(level): def wrapper(func): # error — wrapper must be deeper print(f"Log: {level}") func() # Function is not returned as a decorator return wrapper @log("INFO") def action(): print("Work!")

Pros:

  • Looks simple

Cons:

  • The decorator does not work; the function is executed at the decoration stage, not when action() is called

Positive Case

Using functools.wraps and properly nested functions:

import functools def timer(units): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): import time start = time.time() result = func(*args, **kwargs) end = time.time() if units == 'ms': duration = (end - start) * 1000 else: duration = end - start print(f"Duration: {duration:.4f} {units}") return result return wrapper return decorator @timer('ms') def op(): sum(range(1000)) op()

Pros:

  • Correct structure, easy to extend, clean logs

Cons:

  • Harder to read, especially for beginners