Free lab Real Python 3. Zero installs. Your code stays in this browser. Open the playground

Closures & Decorators

18 min 45 XP
Study loop Read Predict Run Tweak Prove Review
Lesson 2 of 6 · View course roadmap
Step 1

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:

  • @decorator above a def is pure sugar for func = decorator(func)
  • The decorator returns a wrapper function that runs code before and after calling the original
  • *args, **kwargs in 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 wrapper from the decorator — the decorated function becomes None and 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.

Step 2

Try it yourself

Blank · autosaved

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”.

PYexample.py
+ Enter to run
Output appears here…
Step 3

Pass the challenge +45 XP

Blank · autosaved

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").

Target output
HELLO, ADA
HELLO, GRACE
PYchallenge.py
Run your code to check it…
Step 4

Check your understanding

1. @timer above def slow(): is equivalent to…
2. Why do wrappers use (*args, **kwargs)?
Last step

Your notes (saved on this device)

Tip: use and to move between lessons, K to search everything.

Your next ten minutes

Write Python that does something useful.

Start free. No install, no card, no passive video marathon.

Start learning free → Explore the path