What you'll learn
Decorators wrap a function to add behavior — logging, timing, caching, access checks — without touching the function's own code. You've already met a few (@dataclass, @lru_cache, @property). Now you'll understand exactly how they work and write your own.
By the end you'll be able to:
- Explain what the
@decoratorsyntax really does - Write a decorator that wraps any function
- Preserve metadata with
functools.wraps - Write decorators that take arguments
The core idea
A decorator is just a function that takes a function and returns a new function — closures and higher-order functions from the last module, put to work. The new function usually calls the original with some extra behavior around it:
def loud(func): # takes a function
def wrapper():
print("BEFORE")
func()
print("AFTER")
return wrapper # returns a new function
return wrapper
def greet():
print("Hello")
greet = loud(greet) # wrap it manually
greet()A simple decorator
The @decorator line above a function is just syntactic sugar for greet = loud(greet). To wrap any function, the wrapper accepts *args, **kwargs and passes them through — and returns the original's result:
def loud(func):
def wrapper(*args, **kwargs): # accept ANY arguments
print("BEFORE")
result = func(*args, **kwargs)
print("AFTER")
return result
return wrapper
@loud # syntactic sugar for greet = loud(greet)
def greet(name):
print(f"Hello, {name}")
return name
greet("Ada")Key idea
@loud above greet does exactly one thing: replaces greet with loud(greet). Every decorator, no matter how fancy, is this substitution. Once you see the sugar, decorators stop being mysterious.Preserving metadata
There's a catch: after wrapping, greet.__name__ becomes "wrapper" and its docstring is lost — confusing for debugging and tools. Apply @functools.wraps(func) to the wrapper to copy the original's identity across:
from functools import wraps
def loud(func):
@wraps(func) # copy name, docstring, etc. from func
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@loud
def greet(name):
"Greets a person."
return f"Hi {name}"
print(greet.__name__) # greet (without @wraps it would say 'wrapper')
print(greet.__doc__) # Greets a person.Tip
@wraps(func) to your wrappers. It's a one-line habit that keeps names, docstrings, and signatures intact — so introspection, help text, and debuggers still work on the decorated function.Decorators with arguments
To pass arguments to a decorator (like @repeat(times=3)), you need one more layer: a decorator factory — a function that takes the arguments and returns a decorator. Three nested functions, but the pattern is mechanical:
from functools import wraps
def repeat(times): # a decorator FACTORY
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def ping():
print("ping")
ping()Real-world uses
Decorators shine for cross-cutting concerns — behavior many functions share:
- Timing / logging — measure or record every call.
- Caching —
@lru_cachememoizes results. - Access control —
@login_requiredin web frameworks. - Registration & routing —
@app.route("/")in Flask. - Retries — re-run a flaky call a few times.
Recap & quick check
Key takeaways
- A decorator is a function that takes a function and returns a new (wrapping) function.
- @decorator above a function is sugar for func = decorator(func).
- Make wrappers accept *args, **kwargs and return the result so they work on any function.
- Add @functools.wraps(func) to preserve the original's name, docstring, and signature.
- Decorators that take arguments need an extra layer (a decorator factory); great for logging, caching, auth, routing.
Quick check
1. What does @loud above a function mean?
2. Why do wrappers use *args, **kwargs?
3. What does @functools.wraps(func) do?
4. How do you make a decorator that takes arguments, like @repeat(3)?
5. Which is a typical use of decorators?
One more resource-management pattern completes Phase 4's toolkit. Next up: Module 25 — Context Managers.