Skip to Content

How do you write a decorator that takes its own arguments?

Category: Python Fundamentals

Answer

A two-level closure: outer takes the args and returns the actual decorator; inner takes the function and returns the wrapped function. functools.wraps copies metadata. The pattern shows up everywhere: @retry(max=3), @retry_on(Exception), @cached(ttl=60).

Concrete examples from the fca project context

Example 1

def retry(max=3): def deco(fn): @functools.wraps(fn); def wrap(*a, **kw): for i in range(max): try: return fn(*a, **kw); except Exception as e: if i==max-1: raise; return wrap; return deco

Example 2

@retry(max=3) def flaky(): …

Example 3

For class-based decorators, override call to behave like a closure.

Data flow / flow chart

@retry(max=3)
  |
  v
  retry(3) returns deco
  deco(flaky) returns wrapped

Takeaway

Two-level closure + @functools.wraps is the recipe. Memorize the skeleton.