Closures and Decorators

Python 3.14 · ✓ verified by execution on 2026-07-28

Closures and Decorators

Closures

A closure is a function that remembers the variables from where it was defined, even after that outer function has returned. The values travel with the function rather than being looked up in the global namespace later.

When you define a function inside another function, the inner function has access to the outer function’s scope. This is called a closure:

python
def outer_func(msg):
    def inner_func():
        print(f"Message: {msg}")
    return inner_func

my_closure = outer_func("Hello World")
my_closure()
Output
Message: Hello World

Decorators

A decorator is just a function that takes a function and returns a replacement. Everything else about decorators follows from that one sentence.

Decorators are built on top of closures. They take a function as an argument, and return a new function (the wrapper) that adds some behavior before or after calling the original function:

python
def my_decorator(func):
    def wrapper():
        print("Before the function runs")
        func()
        print("After the function runs")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
Output
Before the function runs
Hello!
After the function runs

The @ syntax is pure convenience. Writing @timer above def work(): is exactly the same as writing work = timer(work) afterwards — the decorator runs once, at definition time, and rebinds the name.

Here is an IP01 interactive visualizer that shows how the @my_decorator syntax is exactly the same as calling say_hello = my_decorator(say_hello):

Click to view state suspension visualizer
python · visualize
def my_decorator(func):
    def wrapper():
        print("Before")
        func()
        print("After")
    return wrapper

def say_hello():
    print("Hello")

say_hello = my_decorator(say_hello)
say_hello()

Decorators with Arguments

To decorate functions that take arguments, the inner wrapper function must accept them:

python
def twice(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper

@twice
def greet(name):
    print(f"Hello {name}")

greet("Alice")
Output
Hello Alice
Hello Alice

Challenges

Complete the interactive challenges below to earn XP!

Check yourself

Is the following statement true or false? 'Decorators must use the @ symbol to work.'

Reveal answer

False — The @ symbol is just syntactic sugar. You can manually apply a decorator by doing `func = my_decorator(func)`.

Is the following statement true or false? 'Closures cannot modify the variables they capture.'

Reveal answer

False — Closures can modify captured variables if you use the nonlocal keyword.

Which of these accurately describes a decorator?

Reveal answer

A function returning another function. — A decorator is usually a function returning another function, applied as a transformation.

Challenges

Challenge 1 +25 XP

Write a decorator `bold` that wraps the output of a function in HTML `<b>` tags.

python
  • Test 1 — expects "<b>hi</b>\n"
Need a hint? (−25% XP)

Your wrapper function should call func() and return the result wrapped in HTML tags.

Show solution (0 XP)
def bold(func):
    def wrapper():
        return f"<b>{func()}</b>"
    return wrapper

@bold
def say_hi():
    return "hi"

print(say_hi())

Challenge 2 +25 XP

Write an outer function `make_counter()` that returns an inner function. The inner function should use the nonlocal keyword to increment and return a count starting at 1.

python
  • Test 1 — expects "1\n2\n"
Need a hint? (−25% XP)

Use nonlocal count inside the inner function.

Show solution (0 XP)
def make_counter():
    count = 0
    def inner():
        nonlocal count
        count += 1
        return count
    return inner

counter = make_counter()
print(counter())
print(counter())