Explain what @decorator above a function does. Write a decorator that prints how long a function took, and explain why functools.wraps is used.
Show the explanation
A decorator is a function that takes a function and returns a function.
@timed
def build_report(): ...is exactly the same as build_report = timed(build_report), run once when the def executes.
This works because functions are first-class objects in Python: they can be passed around and returned. Decorators also rely on closures: an inner function remembers the variables of the function that created it.
import functools, time
def timed(func):
@functools.wraps(func) # copy the name, docstring and so on onto wrapper
def wrapper(*args, **kwargs): # accept any signature
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally: # report even if func raises
print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
return wrapperWhy `functools.wraps`? Without it, build_report.__name__ would be "wrapper" and its docstring would be lost. That confuses logs, debuggers, help() and frameworks that look up functions by name. wraps copies __name__, __doc__, __module__ and __qualname__, and sets __wrapped__ so the original function can still be reached.
Decorators with arguments need one more level, a function that returns a decorator:
def retry(times):
def deco(func):
@functools.wraps(func)
def wrapper(*a, **k):
for attempt in range(times):
try:
return func(*a, **k)
except Exception:
if attempt == times - 1:
raise
return wrapper
return deco
@retry(3)
def fetch(): ...Decorators you will meet: @staticmethod, @classmethod, @property, @functools.lru_cache (memoisation), @dataclasses.dataclass, and route decorators in Flask or FastAPI (@app.get("/")). Stacked decorators apply from the bottom up.
Original UniCircle question. Last reviewed 22 Oct 2026.
