Python Closures
Table of Contents + −
In the last lesson you learned about Decorators. They felt a little bit like magic, right? A function wrapping another function and remembering things. Now we pull back the curtain. The idea that makes decorators work has a name. It is called a closure. Once you get it, decorators stop feeling like magic and start feeling obvious.
🤔 Why Closures?
Here is the pain. Sometimes you want a function that already has a value built into it. Say you want a function that always multiplies by 3. And another that always multiplies by 10. You could write two separate functions by hand. But that gets boring fast. And it does not work well if the number only shows up while the program is running.
A closure fixes this. You write one factory function. You tell it the number once. It hands you back a small function that remembers that number forever.
🧩 What Is a Closure?
A closure is an inner function that remembers values from the outer function where it was created. It keeps those values even after the outer function has finished running.
Read that last part again. Even after the outer function has finished. Normally when a function returns, all its local variables are gone. Python cleans them up. But if an inner function still needs one of them, Python keeps it alive just for that inner function. That kept-alive value is what makes it a closure.
Think of it like a lunchbox. In the morning Riya packs a lunchbox at home. Then she leaves the house. The kitchen is closed and empty now. But she still has the food. She carried it with her in the box. The inner function is Riya. The lunchbox is the closure. The food inside is the remembered value from the outer function.
🛠️ The Syntax
A closure needs three things to exist:
- An outer function that holds a variable.
- An inner function defined inside it that uses that variable.
- The outer function returning the inner function. Returning it, not calling it.
Here is the smallest possible closure. The outer function holds a greeting. The inner function uses it.
def make_greeter(greeting): def greet(name): return f"{greeting}, {name}!" return greet # return the function itself, no parentheses
say_hello = make_greeter("Hello")say_hi = make_greeter("Hi")
print(say_hello("Alex"))print(say_hi("Riya"))Output
Hello, Alex!Hi, Riya!Let us walk through what just happened, line by line.
make_greeter("Hello")runs and reachesreturn greet. At this pointmake_greeteris finished.- Normally
greetingwould now disappear. Butgreetstill needs it. So Python keeps"Hello"attached to that returned function. - We saved that returned function in
say_hello. Sosay_hellois a small function that secretly carries"Hello"inside it. say_hicarries"Hi"in its own separate box. The two do not share. Each closure keeps its own copy.
That is the key insight. Notice we wrote return greet with no parentheses. We are returning the function object itself, not the result of calling it. If you wrote return greet() you would get an error. That is because greet needs a name and we have not given one yet.
🔢 The Multiplier Example
This is the example you will see everywhere. So let us build it properly. We want a factory that makes multiplier functions. You give it a number n. It gives you back a function that multiplies anything by n.
The code is short. Read it first, then we will break it down.
def make_multiplier(n): def multiply(x): return x * n # n is remembered from the outer function return multiply
times_three = make_multiplier(3)times_ten = make_multiplier(10)
print(times_three(5)) # 5 * 3print(times_ten(5)) # 5 * 10print(times_three(100)) # 100 * 3Output
1550300Here is what each piece is doing:
make_multiplier(3)runs, definesmultiply, and returns it. The3gets remembered inside that returned function.times_threeis now a function that means “multiply by 3”. It never forgets its 3.make_multiplier(10)does the same thing but remembers 10 instead. Sotimes_tenmeans “multiply by 10”.- When we call
times_three(5), Python looks forx(that is 5) andn(that is the remembered 3), and gives back 15.
See how the two functions keep their own remembered value? times_three carries a 3. times_ten carries a 10. They came from the same factory but they do not get in each other’s way. Each one got its own lunchbox.
Tip
Want to peek inside a closure and see what it remembered? Every closure stores its kept values in a hidden spot you can inspect with __closure__. Try print(times_three.__closure__[0].cell_contents) and Python prints 3. That is proof the value is really being carried around.
🎁 Closures Are the Idea Behind Decorators
Remember decorators from the last lesson? A decorator takes a function, wraps it in another function, and returns the wrapper. That wrapper needs to remember the original function so it can call it later. Remembering something after the outer function finished is exactly what a closure does.
Look at this tiny decorator. The part that “remembers” the original func is a closure.
def announce(func): def wrapper(): print("About to run the function...") func() # func is remembered through the closure print("Done running it.") return wrapper
@announcedef say_hi(): print("Hi there!")
say_hi()Output
About to run the function...Hi there!Done running it.The wrapper function uses func even though announce already finished. That only works because of a closure. So every decorator you write is quietly using the closure idea you just learned. So a decorator is really just a closure that wraps another function.
⚠️ Common Mistakes
A few things trip people up the first time. Watch out for these.
- Returning the call instead of the function. Writing
return greet()runsgreetright now and returns its result. You wanted to hand back the function so you could call it later.
# ❌ Avoid: this calls greet right away and returns its result (and errors)def make_greeter(greeting): def greet(name): return f"{greeting}, {name}!" return greet() # oops, parentheses
# ✅ Good: return the function object, no parenthesesdef make_greeter(greeting): def greet(name): return f"{greeting}, {name}!" return greet- Trying to change the remembered variable. A plain inner function can read the outer variable but not reassign it. If you try, Python thinks you mean a new local variable.
# ❌ Avoid: this raises an error because Python treats count as localdef make_counter(): count = 0 def increment(): count = count + 1 # error: count used before assignment return count return increment
# ✅ Good: tell Python you mean the outer count with nonlocaldef make_counter(): count = 0 def increment(): nonlocal count count = count + 1 return count return increment- The loop variable surprise. If you build closures inside a loop and they all use the loop variable, they may all end up sharing the final value. Bind it as a default argument (
def f(x=i)) to give each closure its own copy.
✅ Best Practices
Keep these habits and closures stay simple and friendly.
- Name the factory after what it builds.
make_multiplier,make_greeter,build_validator. The name should tell the reader they get a function back. - Keep the inner function small. A closure is best when the inner function does one clear job with the remembered value.
- Reach for
nonlocalonly when you truly need to change the remembered value, like a counter. If you are only reading it, you do not neednonlocalat all. - If a closure starts holding lots of state, a class might be clearer. A closure is great for one or two remembered values, not a whole pile.
🧩 What You’ve Learned
You now understand the idea that powers decorators. Quick recap.
- ✅ A closure is an inner function that remembers values from its outer function, even after that outer function has finished.
- ✅ You build one with an outer factory function, an inner function that uses an outer variable, and a
returnof the inner function with no parentheses. - ✅
make_multiplier(n)returns a function that always multiplies by thatn, and each returned function keeps its own remembered value. - ✅ Decorators work because of closures. The wrapper remembers the original function after the outer function finished.
- ✅ Use
nonlocalwhen the inner function needs to change the remembered value, not just read it.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What makes an inner function a closure?
Why: A closure is an inner function that keeps using variables from the outer function even after the outer function returned.
- 2
In make_multiplier(n), why do you write 'return multiply' and not 'return multiply()'?
Why: Without parentheses you return the function object so it can be called later; with parentheses you would call it now and return its result.
- 3
If times_three = make_multiplier(3) and times_ten = make_multiplier(10), what do they share?
Why: Each call to the factory creates a separate closure, so times_three carries 3 and times_ten carries 10 independently.
- 4
Why do decorators rely on closures?
Why: A decorator's wrapper keeps using the original function after the decorator function returned, which is exactly what a closure does.
🚀 What’s Next?
You have seen how a function can remember state and carry behavior around. Next we use a similar setup trick to manage resources cleanly. Think of files and connections that must open and then always close.