Closures & Decorators
Lesson 2 of 6 · View course roadmap
Learn the idea
In Python, functions are values: you can pass them around, store them and return them. A closure is an inner function that remembers variables from the function that created it — even after that outer function has finished.
A decorator uses closures to wrap extra behaviour around a function without touching its body:
@decoratorabove adefis pure sugar forfunc = decorator(func)- The decorator returns a
wrapperfunction that runs code before and after calling the original *args, **kwargsin the wrapper lets it wrap any signature
This is how @app.get(...) in Flask, @lru_cache, @dataclass and pytest fixtures all work — decorators are the backbone of professional Python APIs.
Where you'll use this
Open any production codebase and the decorators pile up: @app.route in Flask, @pytest.fixture, @login_required, @retry, @celery.task. Whole frameworks are decorator-driven because they add behaviour without touching business logic.
Common mistakes
- Forgetting to
return wrapperfrom the decorator — the decorated function becomesNoneand every call raises TypeError. - Forgetting to return the inner function's result from the wrapper — the wrapped function suddenly returns None everywhere.
- Losing the function's name and docstring: wrap the wrapper with
@functools.wraps(func)so introspection and debugging still work.
Pro tip
A decorator that takes arguments, like @retry(times=3), is a function that returns a decorator — three nested defs. Write the plain decorator first, then wrap it once more for the arguments.
Try it yourself
The lesson example is loaded and ready — press Run, then change something and run it again. Breaking it is part of learning. Want a clean slate? Tap “New blank”.
Output appears here…
Pass the challenge +45 XP
Write a decorator shout that uppercases whatever string the wrapped function returns. Apply it to greet(name), which returns f"hello, {name}", then print greet("ada") and greet("grace").
HELLO, ADA HELLO, GRACE
The wrapper calls func(*args, **kwargs), then returns result.upper(). Don't forget to return the wrapper from shout.
def shout(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
@shout
def greet(name):
return f"hello, {name}"
print(greet("ada"))
print(greet("grace"))
Run your code to check it…
Check your understanding
Your notes (saved on this device)
Tip: use ← and → to move between lessons, ⌘K to search everything.