Python
decorators
programming
coding
software development

Decorators with parameters?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A normal Python decorator takes a function and returns a wrapped function. A decorator with parameters adds one more level: first you configure the decorator, then that configured decorator wraps the target function.

Understand the Three Nested Functions

The structure is easiest to remember if you break it into layers:

  • outer function receives decorator arguments
  • middle function receives the function being decorated
  • inner wrapper receives the function call arguments

Here is a complete example:

python
1from functools import wraps
2
3def repeat(times):
4    def decorator(func):
5        @wraps(func)
6        def wrapper(*args, **kwargs):
7            result = None
8            for _ in range(times):
9                result = func(*args, **kwargs)
10            return result
11        return wrapper
12    return decorator
13
14
15@repeat(3)
16def greet(name):
17    print(f"Hello, {name}")
18
19
20greet("Alice")

The key point is that repeat(3) runs first and returns the actual decorator. Then that decorator receives greet.

Why Parameters Are Useful

Decorator parameters let you reuse the same decorator logic with different behavior. Common use cases include:

  • logging with different labels
  • retry decorators with configurable retry counts
  • permission checks by role name
  • caching with different time-to-live values

Without parameters, you often end up copying similar decorators just to change one constant.

For example, a logging decorator:

python
1from functools import wraps
2
3def log_calls(label):
4    def decorator(func):
5        @wraps(func)
6        def wrapper(*args, **kwargs):
7            print(f"[{label}] calling {func.__name__}")
8            return func(*args, **kwargs)
9        return wrapper
10    return decorator
11
12
13@log_calls("service")
14def add(a, b):
15    return a + b
16
17
18print(add(2, 3))

This is the same pattern with a different purpose.

functools.wraps Matters

Always prefer @wraps(func) inside the wrapper. Without it, the decorated function loses metadata such as:

  • '__name__'
  • '__doc__'
  • '__module__'

That hurts debugging, help text, and introspection.

This is especially important in frameworks that inspect function metadata, such as web routing, CLI tooling, and test frameworks.

Decorator Arguments Versus Function Arguments

One common point of confusion is the difference between:

  • arguments passed to the decorator
  • arguments passed when the decorated function is called

In this example:

python
@repeat(3)
def greet(name):
    ...

3 is a decorator argument. Later, when you call greet("Alice"), "Alice" is a normal function argument handled by the wrapper.

Keeping those roles separate makes the nesting much easier to reason about.

Common Pitfalls

The biggest mistake is forgetting one of the function layers. If your decorator takes parameters, you cannot write only one wrapper around the target function. You need the outer configuration step first.

Another issue is forgetting to return the decorator from the outer function or the wrapper from the decorator. Missing one return usually leads to NoneType errors or confusing decoration behavior.

Developers also often omit *args and **kwargs, which makes the decorator work only for very specific function signatures. Unless the signature is intentionally fixed, pass through both.

Finally, use wraps. It is a small addition that prevents unnecessary debugging pain later.

Summary

  • A decorator with parameters adds an outer configuration function.
  • The typical pattern is outer function, decorator function, then wrapper function.
  • Decorator arguments are different from the decorated function's call arguments.
  • 'functools.wraps preserves useful metadata and should usually be included.'
  • Parameterized decorators are ideal for reusable behaviors such as retries, logging, and access checks.
  • The outermost function exists only to capture decorator configuration before decoration happens.
  • Testing a parameterized decorator is easier when the wrapper behavior stays small and side effects are explicit.
  • When stacking parameterized decorators, read them from the bottom up because the nearest decorator wraps the function first.
  • Clear names on decorator factories make debugging wrapped functions much easier.

Course illustration
Course illustration

All Rights Reserved.