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:
- Local
- Enclosing
- Global
- Built-in
Example:
x = "global ๐"
def outer():
x = "outer ๐ "
def inner():
x = "local ๐๏ธ"
print(x)
inner()
outer()
Output:
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:
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:
def make_multiplier(factor):
def multiply(number):
return number * factor
return multiply
double = make_multiplier(2)
print(double(10))
Output:
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:
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:
print(double.__closure__)
Output:
(<cell at 0x...: int object at 0x...>,)
Interesting...
Python created a special object called a cell.
Let's inspect its contents:
print(double.__closure__[0].cell_contents)
Output:
2
The value isn't magically remembered.
Python literally stores it inside a closure cell.
Real-World Closure Example
Imagine a password locker:
def password_protector(secret):
def reveal():
return secret
return reveal
locker = password_protector("๐ swordfish")
print(locker())
Output:
๐ 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:
- Accepts another function
- Adds functionality
- Returns a new function
Think of decorators like upgrades for your functions.
Your First Decorator
def greet(func):
def wrapper():
print("๐ Hello traveler!")
func()
print("๐ Goodbye!")
return wrapper
@greet
def say_name():
print("I am Anik")
say_name()
Output:
๐ Hello traveler!
I am Anik
๐ Goodbye!
Pretty cool.
What Does @ Actually Do?
Many developers think decorators are magic.
They're not.
This:
@greet
def say_name():
pass
Is simply shorthand for:
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:
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:
Scope
โ
Closure
โ
Decorator
They build on each other.
Practical Decorator: Timing Functions
A common real-world use case:
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:
Took 2.00s
Useful for profiling code.
Practical Decorator: Logging
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.
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
@app.route("/")
FastAPI
@app.get("/")
Django
@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:
function.__closure__
and
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.
@decorator
def func():
pass
is equivalent to:
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.



