🧙 Scopes, Closures, and Decorators in Python: A Fun Deep Dive with Magic Behind the Scenes


Imagine Python as Hogwarts. Variables are spells, functions are wizards, and decorators are those cool magical gadgets Fred & George sell at the joke shop. To truly master the magic, you must understand three things: Scopes, Closures, and Decorators.
Grab your butterbeer, this is going to be fun 🍻.
🎭 The Stage: What are Scopes?
In Python, scope is like a stage where variables live.
Think of it like this:
Global scope → The big wide world 🌍 (anyone can see you).
Local scope → Your room 🛏️ (only you can see what’s inside).
Enclosing scope → Your parents’ house 🏠 (you don’t own it, but you can access it).
Built-in scope → The universe 🌌 (Python’s gods decided
len
,print
, etc. just exist).
👉 Python follows the LEGB Rule (Local → Enclosing → Global → Built-in) to decide where to find a variable.
Example:
x = "global 🌍"
def outer():
x = "outer 🏠"
def inner():
x = "local 🛏️"
print(x)
inner()
outer() # Output: local 🛏️
Why? Because Python looks inside → parent → global → built-in until it finds the variable. If not found… you get the dreaded NameError
.
🔮 Behind the Scenes: __globals__
and __builtins__
Every function in Python carries a backpack 🎒 of context.
__globals__
→ Points to the global variables it can see.__builtins__
→ The universal spells (len
,print
, etc.).
def magic():
pass
print(magic.__globals__.keys()) # shows global stuff
This is Python’s way of keeping track of "who lives where."
🌀 Enter Closures: Functions That Remember
A closure is when an inner function remembers variables from its enclosing scope, even if the outer function is gone.
It’s like your grandma’s recipe 🍲 she’s not in the kitchen anymore, but you still have access to her secret ingredients.
Example:
def make_multiplier(factor):
def multiply(number):
return number * factor
return multiply
double = make_multiplier(2)
print(double(10)) # 20
Even though make_multiplier
is done executing, multiply
remembers the factor
.
🧩 Behind the Scenes: The __closure__
& __cell__
Now the real magic 🔥.
print(double.__closure__)
Output:
(<cell at 0x...: int object at 0x...>,)
That cell object (__cell__
) is Python’s horcrux 🧙♂️ it holds the preserved value from the outer function. Without it, closures wouldn’t exist.
You can even peek inside:
print(double.__closure__[0].cell_contents) # 2
So closures aren’t just “functions remembering stuff.”
They literally carry tiny memory cells (__cell__
) with them.
🎭 Real-Life Closure Example: Password Protector
def password_protector(secret):
def get_password():
return secret
return get_password
locker = password_protector("🔑 swordfish")
print(locker()) # 🔑 swordfish
The variable secret
lives on, tucked safely inside a closure cell. Even though password_protector
is long gone, the inner function clings to it like a diary.
🎁 Enter Decorators: Functions That Wrap Functions
If closures are memory keepers, decorators are fashion stylists 👗. They take a function, dress it up with new abilities, and send it back on stage.
Example:
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!
That @greet
line is just Python sugar 🍭 for:
say_name = greet(say_name)
⏱️ Decorator Applications in Real Life
- Timer Decorator ⏰
Measure how long a function takes:
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_function():
time.sleep(2)
slow_function()
- Logger Decorator 📜
Track what’s happening:
def logger(func):
def wrapper(*args, **kwargs):
print(f"🚀 Calling {func.__name__} with {args} {kwargs}")
return func(*args, **kwargs)
return wrapper
- Memoization 🧠
Remember results so Python doesn’t repeat hard work:
def memoize(func):
cache = {}
def wrapper(x):
if x not in cache:
cache[x] = func(x)
return cache[x]
return wrapper
🧪 Behind the Scenes of Decorators
Decorators rely on closures.
The
wrapper
function holds ontofunc
via a closure cell (__cell__
).When you call
say_name
, you’re actually callingwrapper
.
👉 This means scopes → closures → decorators is a natural progression.
They’re not separate ideas, but layers of the same onion 🧅.
🏆 Final Thoughts
Scopes decide “who can see what.”
Closures let functions remember things, thanks to
__closure__
and__cell__
.Decorators let us wrap functions with superpowers.
Next time you write Python, remember: you’re not just coding, you’re performing magic ✨.
💡 Pro tip: Peek inside your functions with __globals__
, __closure__
, and __cell_contents
. It feels like hacking into Python’s brain 🧠.
Subscribe to my newsletter
Read articles from Anik Sikder directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Anik Sikder
Anik Sikder
Full-Stack Developer & Tech Writer specializing in Python (Django, FastAPI, Flask) and JavaScript (React, Next.js, Node.js). I build fast, scalable web apps and share practical insights on backend architecture, frontend performance, APIs, and Web3 integration. Available for freelance and remote roles.