Perfect.
DAY 15 is a turning point — this is where Python goes from “procedural + OOP” to powerful functional runtime behavior.
Most Python devs use decorators.
Very few understand them.
After today, you will.
🧠 DAY 15 — Closures & Decorators
(Free Variables, Cell Objects & Function Wrapping)
🔑 CORE IDEA OF DAY 15
A closure is a function that remembers variables from its enclosing scope, even after that scope is gone.
Decorators are just closures + functions-as-objects.
1️⃣ What Is a Closure? (Precise Definition)
A closure exists when:
- A function is defined inside another function
- The inner function references variables from the outer function
- The inner function survives after the outer function returns
def outer(x):
def inner():
return x
return inner
f = outer(10)
print(f()) # 10
✔ x still exists — even though outer() has finished.
2️⃣ Why Does This Work? (Runtime Truth)
When outer() finishes:
- Its frame is destroyed
- BUT variables referenced by
innerare stored in a cell object
This is the key insight.
3️⃣ Free Variables & Cell Objects (CPython Level)
def outer(x):
def inner():
return x
return inner
f = outer(10)
Inspect:
f.__closure__ # tuple of cell objects
f.__closure__[0].cell_contents # 10
🧠 Cell objects keep data alive beyond frame lifetime
4️⃣ Closure Memory Model (Visual)



Conceptually:
inner function
├── __code__
├── __globals__
└── __closure__ → cell → x = 10
5️⃣ Why Closures Are Powerful
Closures allow:
- State without classes
- Encapsulation
- Function factories
- Decorators
- Callbacks
Example (function factory):
def power(n):
def inner(x):
return x ** n
return inner
square = power(2)
cube = power(3)
Each closure has its own state.
6️⃣ The Famous Late-Binding Trap 🔥🔥🔥
funcs = []
for i in range(3):
def f():
return i
funcs.append(f)
print([f() for f in funcs])
❌ Output:
[2, 2, 2]
WHY?
- Closures capture variables, not values
iis looked up at call time- Loop ends with
i = 2
7️⃣ Correcting Late Binding (Interview Favorite)
Solution 1: Default Argument Trick
funcs = []
for i in range(3):
def f(i=i):
return i
funcs.append(f)
✔ [0, 1, 2]
Why this works:
- Default arguments evaluated immediately
- Value captured, not name
Solution 2: Helper Function
def make_func(i):
def f():
return i
return f
8️⃣ Decorators — What They REALLY Are
A decorator is:
A function that takes a function and returns a function
def decorator(func):
def wrapper():
print("before")
func()
print("after")
return wrapper
Usage:
@decorator
def hello():
print("hello")
Equivalent to:
hello = decorator(hello)
9️⃣ Decorator Execution Order (VERY IMPORTANT)
@decorator
def f():
pass
Execution order:
fis defineddecorator(f)is executed immediately- Result replaces
f
⚠️ Decorators run at definition time, not call time.
🔟 Why functools.wraps Exists (CRITICAL)
Without it:
def decorator(func):
def wrapper():
return func()
return wrapper
You lose:
__name____doc____annotations__
Correct pattern:
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Interviewers expect this.
11️⃣ Decorators with Arguments (Advanced)
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
Usage:
@repeat(3)
def hello():
print("hi")
Decorator layers:
repeat(3) → decorator → wrapper
12️⃣ Closures vs Classes (Design Choice)
Closures:
- Lightweight
- Simple state
- Functional style
Classes:
- Complex state
- Multiple methods
- Clear structure
Senior engineers choose intentionally.
🔥 INTERVIEW TRAPS (DAY 15)
Q1
def outer():
x = 10
def inner():
x += 1
return x
return inner
❌ Error
Why?
- Assignment makes
xlocal - Use
nonlocal x
Q2
def counter():
x = 0
def inc():
nonlocal x
x += 1
return x
return inc
✔ Valid closure-based state
Q3
Why decorators don’t slow code too much?
✔ Wrapper is just one extra function call
✔ Cost predictable
✔ Often negligible compared to I/O
🧠 DAY 15 MENTAL CHECKLIST
When using closures/decorators:
- Am I capturing variable or value?
- Will late binding bite me?
- Do I need
nonlocal? - Did I preserve metadata (
wraps)? - Is closure simpler than a class here?
📝 DAY 15 ASSIGNMENT (MANDATORY)
1️⃣ Predict output (NO RUNNING):
def make_funcs():
return [lambda: i for i in range(3)]
funcs = make_funcs()
print([f() for f in funcs])
2️⃣ Fix it in TWO ways
(Default arg + helper function)
3️⃣ Explain clearly:
- What a cell object is
- Why closures survive after outer returns
🔜 DAY 16 PREVIEW
DAY 16 — Lambda & Functional Programming in Python
You’ll learn:
- When lambdas are good
- When they are dangerous
map,filter,reduceinternals- Functional vs Pythonic style tradeoffs
When ready, say 👉 “START DAY 16”