Back to all tutorials
pythonfunctionsdecoratorsclosurespython-functions-series

Python Closures and Decorators Explained Like Gift Wrapping

Understand closures and decorators in Python using a simple gift-wrapping analogy. Learn how @decorator syntax actually works, step by step.

The Spark Engineer 2026-08-25 10 min read

A gift and its wrapping paper are two different things, but once wrapped, you interact with the whole package as one. You unwrap it, and the original gift is still there underneath, untouched, just presented differently. That's precisely what a decorator does to a function in Python: it wraps it with extra behavior, without changing the original function itself.

Step One: Understanding Closures

Before decorators make sense, you need to understand closures, since decorators are built on top of them.

A closure is a function that remembers variables from the place it was created, even after that outer function has finished running.

Python
def make_greeter(name):    def greet():        print(f"Hello, {name}!")    return greet say_hi_to_alex = make_greeter("Alex")say_hi_to_alex()   # Hello, Alex!

Notice that make_greeter() already finished running by the time say_hi_to_alex() is called. Yet it still remembers name. That's the closure: the inner function "closes over" the variable from its birthplace and carries it along, like a gift box that remembers what's inside it even after it leaves the workshop.

Step Two: A Function That Wraps Another Function

Now let's build something that takes a function as an ingredient and wraps extra behavior around it, exactly like wrapping paper around a gift.

Python
def add_wrapping(func):    def wrapped():        print("🎁 Wrapping paper on")        func()        print("🎁 Wrapping paper off")    return wrapped def give_gift():    print("Here is your gift!") wrapped_gift = add_wrapping(give_gift)wrapped_gift()

Output:

🎁 Wrapping paper on Here is your gift! 🎁 Wrapping paper off

The original give_gift() function never changed. add_wrapping() just built something new around it.

Step Three: The @decorator Shortcut

Writing wrapped_gift = add_wrapping(give_gift) every time is repetitive. Python gives you @ syntax to apply this wrapping automatically.

Python
def add_wrapping(func):    def wrapped():        print("🎁 Wrapping paper on")        func()        print("🎁 Wrapping paper off")    return wrapped @add_wrappingdef give_gift():    print("Here is your gift!") give_gift()

@add_wrapping above def give_gift() means exactly the same thing as the manual version, it's just cleaner to read. This is a decorator: a function that wraps another function to add behavior before or after it runs, without touching its original code.

Handling Functions With Arguments

Real gifts come in different shapes and sizes, and real functions take arguments. Use *args and **kwargs so your decorator works on any function, no matter what it accepts.

Python
def add_wrapping(func):    def wrapped(*args, **kwargs):        print("🎁 Wrapping paper on")        result = func(*args, **kwargs)        print("🎁 Wrapping paper off")        return result    return wrapped @add_wrappingdef give_gift(name):    print(f"Here is your gift, {name}!") give_gift("Sam")

A Real-World Example: Timing a Function

Decorators aren't just a teaching toy, they're everywhere in real Python code. Here's one that measures how long a function takes to run.

PYTHON
import time def timer(func): def wrapped(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} took {end - start:.4f} seconds") return result return wrapped @timer def slow_task(): time.sleep(1) slow_task() # slow_task took 1.0002 seconds

This is exactly the same pattern used by real frameworks: Flask's @app.route, for example, wraps your function to connect it to a web URL, without changing what your function itself does.

What's Next

That wraps up the core series on Python functions, from writing your first def to wrapping functions with decorators. With parameters, *args/**kwargs, lambdas, scope, recursion, and decorators covered, you now have every building block needed to read and write real-world Python code with confidence.

...
Share

Discussion & Thoughts

Join the conversation with your Google Account

0 Comments

Sign in to leave a comment

We use 1-click Google authentication to prevent bot spam and verify authentic developer discussions.

Loading discussions...