By the end of this lesson, you’ll:
Know what a closure is,
Understand how closures work internally,
Learn why we use closures,
See practical real-world examples,
And get practice exercises to solidify your understanding.
1. What is a Closure in Python?
A closure is a function that remembers variables from its enclosing scope, even if that scope is no longer active.
In other words:
A closure allows a function to access variables from outside its immediate scope, even after the outer function has finished executing.
Example (without closure)
def outer_function():
x = 10
def inner_function():
print(x)
inner_function()
outer_function()
Output:
10
Here, inner_function() accesses x from the outer scope — that’s normal.
But now watch what happens if we return the inner function.
2. How Closures Are Created
Step-by-step Example:
def outer_function():
x = 10
def inner_function():
print(x)
return inner_function
my_func = outer_function()
my_func()
Output:
10
What happened internally?
When outer_function() ran, it created x = 10 and defined inner_function.
Then it returned inner_function.
Normally, when a function finishes, its local variables (x) are destroyed.
But because inner_function remembers x, it stays alive inside the returned function.
That “remembering” is called a closure.
3. Checking if a function is a closure
You can inspect the closure using the __closure__ attribute:
def outer_function():
x = 5
def inner_function():
print(x)
return inner_function
f = outer_function()
print(f.__closure__)
Output:
(<cell at 0x...: int object at 0x...>,)
You can even look inside the closure cell:
print(f.__closure__[0].cell_contents)
Output:
5
That’s how Python stores the enclosed variable.
4. Why Use Closures?
Closures are very powerful and used frequently in Python to:
Preserve state without using global variables or classes.
Encapsulate logic neatly.
Build function factories (functions that return customized functions).
Implement decorators (a key part of advanced Python).
Example 1: Function Factory
You can use closures to create customized functions.
def multiplier(factor):
def multiply(number):
return number * factor
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(5))
print(triple(5))
Here:
Each returned function (double, triple) “remembers” its own factor.
factor stays alive even after multiplier() finishes.
Example 2: Simple Counter
Closures can maintain state between function calls.
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
count1 = counter()
print(count1())
print(count1())
print(count1())
Without nonlocal, Python would treat count as a new local variable inside increment().
Example 3: Configuration Wrapper
Suppose you want to wrap behavior around a specific configuration.
def greet_config(lang):
def greet(name):
if lang == "en":
print(f"Hello, {name}!")
elif lang == "es":
print(f"Hola, {name}!")
else:
print(f"Hi, {name}!")
return greet
english_greet = greet_config("en")
spanish_greet = greet_config("es")
english_greet("Alice")
spanish_greet("Carlos")
Output:
Hello, Alice!
Hola, Carlos!
Each function remembers its lang configuration.
5. Important Keyword: nonlocal
When using closures, the inner function cannot modify variables from the enclosing scope unless you declare them as nonlocal.
Example:
def outer():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
f = outer()
print(f())
print(f())
Without nonlocal, you’d get an error:
UnboundLocalError: local variable 'count' referenced before assignment
7. Real-World Use Cases of Closures
Decorators — Python decorators are implemented using closures.
def logger(func):
def wrapper(*args, **kwargs):
print(f"Running {func.__name__}...")
return func(*args, **kwargs)
return wrapper
@logger
def greet(name):
print(f"Hello, {name}")
greet("John")
Data hiding — Closures let you encapsulate variables.
Callbacks — Used in event handling (e.g., in GUIs or async code).
Custom Function Generators — Like multiplier() above.
8. Practice Exercises
Exercise 1: Function Factory
Create a closure power_of(n) that returns a function which raises any number to the power n.
Expected:
square = power_of(2)
cube = power_of(3)
print(square(4))
print(cube(2))
Exercise 2: Simple Banking Example
Create a closure bank_account() that:
Starts with a balance of 0.
Has an inner function transaction(amount) that can deposit or withdraw.
Uses nonlocal to modify the balance.
Expected:
account = bank_account()
print(account(100))
print(account(-50))
9. Summary
| Concept | Meaning |
| Closure | A function that remembers variables from its enclosing scope |
| How it’s formed | When a nested function references variables from its enclosing function and is returned |
| Use cases | Function factories, decorators, maintaining state, data hiding |
| Keyword | nonlocal allows modification of enclosed variables |
Key Takeaways
Closures make functions stateful without using classes.
They are foundational for decorators and many advanced Python features.
They provide encapsulation and flexibility in functional programming styles.