- __init__() function
- Aliases
- and operator
- argparse
- Arrays
- Booleans
- Break statement
- Bytes
- Classes
- Closure
- Code blocks
- Comments
- Conditional statements
- Console
- Context manager
- Data class
- Data structures
- Data visualization
- datetime module
- Decorator
- Dictionaries
- Django
- Docstrings
- Encapsulation
- enum
- enumerate() function
- Equality operator
- Error handling
- Exception handling
- False
- File handling
- Filter()
- Flask framework
- Floats
- Floor division
- For loops
- Formatted strings
- Functions
- Generator
- Globals()
- Greater than operator
- Greater than or equal to operator
- If statement
- in operator
- Indices
- Inequality operator
- Inheritance
- Integers
- Iterator
- Lambda function
- len() Function
- Less than operator
- Less than or equal to operator
- List append() method
- List comprehension
- List count()
- List insert() method
- List pop() method
- List reverse() method
- List sort() method
- Lists
- Logging
- map() function
- Match statement
- Math module
- Merge sort
- Min()
- Modules
- Modulo operator
- Multiline comment
- Multiprocessing
- Multithreading
- None
- not operator
- NumPy library
- OOP
- or operator
- Override method
- Pandas library
- Parameters
- pathlib module
- Pickle
- Polymorphism
- print() function
- Property()
- Protocol
- Random module
- range() function
- Raw strings
- Recursion
- Reduce()
- Regular expressions
- requests Library
- return statement
- round() function
- Script
- Sets
- SQLite
- String decode()
- String find()
- String join() method
- String replace() method
- String split() method
- String strip()
- Strings
- Ternary operator
- time.sleep() function
- True
- try...except statement
- Tuples
- Type casting
- Variables
- Virtual environment
- What is Python?
- While loops
- Zip function
PYTHON
Python Closure: Syntax, Usage, and Examples
A Python closure is a function that remembers values from the scope where it was created, even after that scope has finished running. In practice, a closure lets a function “carry” some data around with it.
How to Use Closures in Python
A closure appears when three conditions are met:
Learn Python on Mimo
- You define a function inside another function.
- The inner function refers to variables from the outer function.
- The outer function returns the inner function.
Here is the basic syntax:
Python
def outer(x):
def inner(y):
return x + y
return inner
Breaking this down:
outeris the enclosing function. It takes a parameterx.inneris the nested function. It refers tox, which lives in the outer scope.outerreturnsinner, not the result of callinginner.
Using the closure:
Python
add_10 = outer(10)
print(add_10(5)) # 15
print(add_10(20)) # 30
Here, add_10 is a closure. It remembers x = 10 from the call to outer(10), even though outer has already finished.
Capturing Variables From the Outer Scope
The key idea is that the inner function does not copy the value. It keeps a reference to the variable in the outer scope.
Python
def make_multiplier(factor):
def multiply(value):
return factor * value
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(8)) # 16
print(triple(8)) # 24
Each closure (double, triple) remembers a different factor.
Modifying Captured State With nonlocal
By default, you can read captured variables but not assign to them. To update a variable from the outer function, use nonlocal.
Python
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
countlives in the outer function.nonlocal counttells Python that assignments tocountshould affect the outercount, not create a new local variable.
When to Use Closures in Python
Closures are handy any time you want to combine behavior with a bit of remembered state, without creating a full class. Here are some common situations.
1. Function Factories
Sometimes you want to generate functions with slightly different behavior. Closures let you “configure” a function once and then reuse it.
Examples:
- Creating different discount calculators with fixed percentages.
- Building validators with different rules (e.g., minimum length).
- Generating SQL query builders with fixed table names.
2. Encapsulating State Without Classes
A closure can hold onto data that you do not want to expose directly. That gives you a simple form of encapsulation.
You might:
- Keep an internal counter.
- Track the last few values processed.
- Store some configuration that should not be modified directly.
The caller gets a function to call, not a whole object with many attributes.
3. Callbacks and Event Handlers
In asynchronous code, GUI apps, or web frameworks, you often pass callbacks around. Closures let those callbacks remember context.
For example:
- A button click handler that remembers which user is signed in.
- A network callback that remembers the URL it fetched.
- A retry function that remembers how many attempts are allowed.
Instead of passing extra parameters everywhere, you let the closure capture the data it needs.
4. Lightweight Decorators
Many decorators use closures behind the scenes. A decorator often returns a new function that wraps another function, and a closure captures the original function and any extra configuration.
For small, one-off decorators, a closure can feel simpler than building a whole class.
Examples of Python Closures
Let’s walk through several examples that mirror real use cases.
Example 1: Creating Simple Function Factories
Imagine a small app that applies different tax rates depending on where someone lives. You could write separate functions for each place, or generate them from a closure.
Python
def make_tax_calculator(rate):
def calculate(price):
return price + price * rate
return calculate
us_tax = make_tax_calculator(0.07)
eu_tax = make_tax_calculator(0.20)
print(us_tax(100)) # 107.0
print(eu_tax(100)) # 120.0
Here:
rateis captured by the closure.us_taxandeu_taxare functions with different rememberedratevalues.
You avoid repeating the same logic and keep the code easy to read.
Example 2: A Counter With Hidden State
This pattern comes up often when you need a counter but do not want to expose a mutable global.
Python
def make_id_generator(prefix: str):
current = 0
def next_id():
nonlocal current
current += 1
return f"{prefix}-{current}"
return next_id
ticket_id = make_id_generator("TICKET")
print(ticket_id()) # TICKET-1
print(ticket_id()) # TICKET-2
print(ticket_id()) # TICKET-3
currentis hidden insidemake_id_generator.- Only
next_id()can changecurrent. - The calling code has no direct access to modify
currentincorrectly.
You get a simple, controlled way to generate IDs, without designing a full class.
Example 3: Adding Context to Logging
Logging often needs context: which module, which user, which request. A closure can add that context without passing it into every call.
Python
from datetime import datetime
def make_logger(prefix: str):
def log(message: str) -> None:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] [{prefix}] {message}")
return log
auth_log = make_logger("AUTH")
payment_log = make_logger("PAYMENT")
auth_log("Login successful for user alice@example.com")
payment_log("Processed order #5321")
Each closure remembers a different prefix, so the output stays easy to scan.
Example 4: Using Closures With Sorting
Python’s sorted() and list.sort() accept a key function. A closure can produce a key function that remembers how to sort.
Python
def sort_by_field(field_name):
def key_func(item):
return item[field_name]
return key_func
users = [
{"username": "sam", "age": 35},
{"username": "alex", "age": 29},
{"username": "jordan", "age": 31},
]
by_age = sort_by_field("age")
by_username = sort_by_field("username")
print(sorted(users, key=by_age))
print(sorted(users, key=by_username))
The key_func closure captures field_name, so you can reuse the sorting logic with different fields.
Example 5: A Minimal Decorator Using a Closure
Closures also power decorators. Here’s a simple timing decorator:
Python
import time
from functools import wraps
def timed(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
duration = time.perf_counter() - start
print(f"{func.__name__} took {duration:.4f} seconds")
return result
return wrapper
@timed
def slow_add(x, y):
time.sleep(0.5)
return x + y
print(slow_add(3, 4))
Here:
wrapperis a closure that capturesfunc.startanddurationare local to each call, butfuncstays bound to the original function.
Learn More About Python Closures
Once you understand the basic pattern, a few more details help you use closures effectively and avoid surprises.
Scope Rules and Free Variables
Closures rely on Python’s scope rules. A variable from the outer function that is used inside the inner function becomes a free variable in the closure.
Quick reminders:
- Names created inside a function are local to that function.
- Names from enclosing functions are available to nested functions.
- Closures keep those names alive as long as the inner function exists.
You can inspect a function’s closure in code:
Python
def make_adder(x):
def add(y):
return x + y
return add
add_5 = make_adder(5)
print(add_5.__closure__) # Tuple of cell objects holding captured variables
You rarely need to look at __closure__ in daily work, but it can help during debugging.
Late Binding and Loop Gotchas
One common pitfall with closures in Python is late binding. The inner function looks up variables when it runs, not when it’s defined.
Consider this example:
Python
funcs = []
for i in range(3):
def show():
print(i)
funcs.append(show)
for f in funcs:
f() # What do you expect?
The output is:
All closures share the same i, which ends as 2. To capture the current value at each iteration, use a default argument or another closure layer:
Using a default argument:
Python
funcs = []
for i in range(3):
def show(i=i):
print(i)
funcs.append(show)
for f in funcs:
f()
# 0
# 1
# 2
Using a helper closure:
Python
def make_show(n):
def show():
print(n)
return show
funcs = [make_show(i) for i in range(3)]
for f in funcs:
f()
# 0
# 1
# 2
Both techniques “freeze” the value at the time of creation.
Closures vs. Classes
Many examples that use closures could also be written using classes. For instance, the counter closure could become:
Python
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
counter = Counter()
print(counter.increment())
print(counter.increment())
How do you decide between them?
- Use a closure when you need a small piece of behavior with a bit of hidden state and a simple interface.
- Use a class when the concept has several operations, rich behavior, or needs inheritance.
Sometimes a closure is the quick, lightweight solution; sometimes a class keeps things clearer.
Performance and Memory Thoughts
A closure keeps references to captured variables. In most cases this cost is tiny. Still, a few habits help keep code healthy:
- Avoid capturing large objects (like big dataframes or lists) unless you truly need them.
- Avoid creating huge numbers of closures in tight loops if you can reuse a smaller number.
- Remember that closures keep their environment alive; dropping references to closures lets Python garbage-collect that state.
You usually don’t need to micro-optimize closures, but awareness helps when performance matters.
How Closures Show Up in Real Code
Even if you never explicitly say “I’m writing a closure now,” you will use them in many idioms:
- Sorting with a key function built on the fly.
- Building URL routers where each route handler remembers its path pattern.
- Writing decorators for caching, retry logic, or access control.
- Creating small configuration helpers, like “loggers with prefixes” or “database query builders with a default schema.”
After a while, closures feel less like a special feature and more like a natural tool for capturing context.
Summary
A Python closure is a function that remembers values from its enclosing scope. You create one by defining a function inside another function, letting the inner function use variables from the outer function, and returning the inner function.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.