Anik Sikder
Technical Writing/python/scopes-closures-and-decorators-in-python-a-fun-deep-dive-with-magic-behind-the-scenes
article.sh

$ open article

python

Scopes, Closures, and Decorators in Python Explained

8 min readโ€ขAugust 28, 2025
Python Scopes Closures and Decorators Visualization

Scopes, Closures, and Decorators in Python Explained ๐Ÿง™

Hey Python enthusiasts! ๐Ÿ‘‹

Have you ever wondered:

  • Why can an inner function access variables from an outer function?
  • How does Python know where to find a variable?
  • What exactly is happening when you write @decorator?
  • Why do closures seem to "remember" variables after a function has finished running?

If those questions have ever crossed your mind, you're in the right place.

Today we're diving into three of Python's most powerful concepts:

  • Scopes
  • Closures
  • Decorators

And trust me, once you understand how these fit together, a huge chunk of Python suddenly starts making sense.

Grab your coffee โ˜• and let's explore the magic.


The Stage: Understanding Scope in Python

Before we can understand closures or decorators, we need to understand where variables live.

In Python, a scope determines where a variable is visible and accessible.

Think of scopes like rooms inside a house.

  • Local Scope โ†’ Your room ๐Ÿ›๏ธ
  • Enclosing Scope โ†’ Your family's house ๐Ÿ 
  • Global Scope โ†’ Your neighborhood ๐ŸŒ
  • Built-in Scope โ†’ The entire universe ๐ŸŒŒ

Python follows something called the LEGB Rule when searching for variables.

LEGB Rule

Python searches in this order:

  1. Local
  2. Enclosing
  3. Global
  4. Built-in

Example:

code
x = "global ๐ŸŒ"

def outer():
    x = "outer ๐Ÿ "

    def inner():
        x = "local ๐Ÿ›๏ธ"
        print(x)

    inner()

outer()

Output:

code
local ๐Ÿ›๏ธ

Python finds x inside inner() first, so it stops searching.


How Python Looks Up Variables

Every function carries references to its surrounding environment.

Let's inspect one:

code
def magic():
    pass

print(magic.__globals__.keys())

You'll see a dictionary containing the global variables available to that function.

Python internally stores:

  • __globals__
  • __builtins__

These help Python resolve variable names during execution.

Think of them as the function's map of the world.


Closures: Functions That Remember

Now comes the really interesting part.

A closure happens when an inner function remembers variables from its enclosing scope even after the outer function has finished executing.

Let's see one:

code
def make_multiplier(factor):
    def multiply(number):
        return number * factor

    return multiply

double = make_multiplier(2)

print(double(10))

Output:

code
20

Wait...

make_multiplier() already finished running.

Why does multiply() still know about factor?

That's exactly what a closure is.

The inner function remembers its environment.


Closures Are Like Carrying a Backpack ๐ŸŽ’

Imagine the inner function leaves home but takes a backpack containing everything it still needs.

That backpack contains:

code
factor = 2

Whenever the function runs, it can still access those values.

That's the magic of closures.


Looking Inside a Closure

Python actually stores closure data internally.

Let's inspect it:

code
print(double.__closure__)

Output:

code
(<cell at 0x...: int object at 0x...>,)

Interesting...

Python created a special object called a cell.

Let's inspect its contents:

code
print(double.__closure__[0].cell_contents)

Output:

code
2

The value isn't magically remembered.

Python literally stores it inside a closure cell.


Real-World Closure Example

Imagine a password locker:

code
def password_protector(secret):
    def reveal():
        return secret

    return reveal

locker = password_protector("๐Ÿ”‘ swordfish")

print(locker())

Output:

code
๐Ÿ”‘ swordfish

Even though password_protector() is gone, the inner function still remembers the secret.


What Are Decorators?

Now we're ready for decorators.

Decorators are one of Python's most powerful features.

A decorator is simply a function that:

  1. Accepts another function
  2. Adds functionality
  3. Returns a new function

Think of decorators like upgrades for your functions.


Your First Decorator

code
def greet(func):

    def wrapper():
        print("๐Ÿ‘‹ Hello traveler!")
        func()
        print("๐ŸŽ‰ Goodbye!")

    return wrapper


@greet
def say_name():
    print("I am Anik")

say_name()

Output:

code
๐Ÿ‘‹ Hello traveler!
I am Anik
๐ŸŽ‰ Goodbye!

Pretty cool.


What Does @ Actually Do?

Many developers think decorators are magic.

They're not.

This:

code
@greet
def say_name():
    pass

Is simply shorthand for:

code
def say_name():
    pass

say_name = greet(say_name)

That's it.

The decorator wraps your original function and returns a new version.


Decorators Are Built on Closures

Here's the important connection:

code
def greet(func):

    def wrapper():
        func()

    return wrapper

Notice something?

The inner function uses func from the enclosing scope.

That's a closure.

Decorators work because closures exist.

In other words:

code
Scope
   โ†“
Closure
   โ†“
Decorator

They build on each other.


Practical Decorator: Timing Functions

A common real-world use case:

code
import time

def timer(func):

    def wrapper(*args, **kwargs):
        start = time.time()

        result = func(*args, **kwargs)

        end = time.time()

        print(f"Took {end - start:.2f}s")

        return result

    return wrapper


@timer
def slow_task():
    time.sleep(2)

slow_task()

Output:

code
Took 2.00s

Useful for profiling code.


Practical Decorator: Logging

code
def logger(func):

    def wrapper(*args, **kwargs):
        print(
            f"Calling {func.__name__}"
        )

        return func(*args, **kwargs)

    return wrapper

This is commonly used in:

  • APIs
  • Frameworks
  • Monitoring systems
  • Audit trails

Practical Decorator: Memoization

Memoization caches expensive computations.

code
def memoize(func):

    cache = {}

    def wrapper(x):

        if x not in cache:
            cache[x] = func(x)

        return cache[x]

    return wrapper

This can dramatically improve performance.


How Decorators Power Popular Frameworks

If you've used:

Flask

code
@app.route("/")

FastAPI

code
@app.get("/")

Django

code
@login_required

You've already been using decorators.

Frameworks rely heavily on them because they let developers extend behavior without modifying original code.


The Relationship Between Scopes, Closures, and Decorators

Everything connects beautifully:

Scope

Determines where variables are visible.

Closure

Allows functions to remember variables from surrounding scopes.

Decorator

Uses closures to wrap and enhance functions.

Understanding one naturally leads to understanding the next.


TL;DR Quick Recap

  • Python uses the LEGB rule for variable lookup.
  • Scopes determine variable visibility.
  • Closures allow inner functions to remember outer variables.
  • Closure data is stored inside __closure__ cells.
  • Decorators are functions that wrap other functions.
  • Decorators work because of closures.
  • Frameworks like Flask, FastAPI, and Django heavily use decorators.

Frequently Asked Questions

What is scope in Python?

Scope determines where a variable can be accessed.

Python uses four scopes:

  • Local
  • Enclosing
  • Global
  • Built-in

This is known as the LEGB rule.


What is the LEGB rule?

LEGB stands for:

  • Local
  • Enclosing
  • Global
  • Built-in

Python searches for variables in this order.


What is a closure in Python?

A closure is a function that remembers variables from its enclosing scope even after the outer function has finished executing.


How do closures work internally?

Python stores closure variables inside special cell objects.

You can inspect them using:

code
function.__closure__

and

code
cell.cell_contents

What is __closure__?

__closure__ contains references to variables captured from an enclosing scope.

These values are stored inside closure cells.


What is a decorator?

A decorator is a function that accepts another function, modifies or extends its behavior, and returns a new function.


Are decorators built using closures?

Yes.

Decorators rely heavily on closures because the wrapper function must remember the original function being decorated.


What does the @ symbol do?

The @ syntax is shorthand for applying a decorator.

code
@decorator
def func():
    pass

is equivalent to:

code
func = decorator(func)

Why are decorators useful?

Decorators allow developers to:

  • Add logging
  • Measure performance
  • Add authentication
  • Implement caching
  • Add validation

Without changing the original function.


What frameworks use decorators?

Popular examples include:

  • Django
  • Flask
  • FastAPI
  • Click
  • Typer

Decorators are everywhere in modern Python development.


Can closures improve performance?

Closures themselves don't necessarily improve performance.

However, they enable patterns like memoization and caching that can dramatically speed up applications.


Should I learn closures before decorators?

Absolutely.

Decorators make much more sense once you understand scopes and closures.

Think of decorators as closures applied in a practical way.


Key Takeaways

  • Scopes control variable visibility.
  • Python follows the LEGB lookup rule.
  • Closures allow functions to remember variables.
  • Closure values are stored in cell objects.
  • Decorators wrap functions with additional behavior.
  • Decorators are powered by closures.
  • Understanding these concepts unlocks advanced Python development.

Final Thoughts

Scopes, closures, and decorators are some of the most elegant features in Python.

At first they may seem unrelated.

But once you see how they build upon each other, everything clicks.

The next time you write a decorator, remember:

You're not just adding functionality.

You're leveraging Python's entire scope system, closure mechanism, and function object model to create something powerful.

That's pretty magical. โœจ


About the Author

Anik Sikder is a Software Engineer specializing in Python, Django, FastAPI, System Design, Cloud Infrastructure, Backend Engineering, and Software Architecture.

He writes about Python internals, JavaScript, distributed systems, DevOps, scalable software design, and modern engineering practices.

$ tags

pythonscopesclosuresdecoratorslegbfunctional-programmingsoftware-engineering

$ ls related_articles

status: end_of_file