A decorator is a callable that takes a function (or class) and returns a wrapped version, usually adding behavior before/after the original call without changing its source.
Syntax sugar:
@decorator def f(): ...
is equivalent to f = decorator(f).
Common uses: logging, timing, caching, auth checks, retries, registering plugins.
import time
from functools import wraps
def timed(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return fn(*args, **kwargs)
finally:
elapsed = time.perf_counter() - start
print(f"{fn.__name__} took {elapsed:.4f}s")
return wrapper
@timed
def work(n):
return sum(range(n))
work(100_000)Decorators can take arguments by adding an outer factory layer: @retry(times=3) → factory returns the real decorator.
Interview tip: Mention functools.wraps so the wrapped function keeps its __name__ and docstring.