Python Closures
1. What is a Closure?
A closure is a function object that retains access to variables from its enclosing scope, even after the outer function has finished executing.
In simple terms, a closure allows a nested function to "remember" the environment in which it was created.
2. Why Use Closures?
Closures are used for:
- Data hiding / encapsulation
- Factory functions
- Callback functions
- Avoiding use of global variables
- Maintaining state without using classes
3. Understanding with an Example
Basic Structure:
Code & explanation
-
inner_function
is defined inside outer_function
.- It remembers the value of
msg
even though outer_function
has finished executing.-
greet
becomes a closure.
4. Checking if a Function is a Closure
print(greet.__closure__) # Contains cell objects with enclosed variables
print(greet.__closure__[0].cell_contents) # Output: Hello, Abhijit!
5. Another Example: Counter Function
Code & explanation
- Each
make_counter()
call creates a new scope.- The nested
counter()
remembers count
through closure.-
nonlocal
allows modifying the enclosing scope’s variable.
6. Common Mistake with Closures in Loops: Late Binding
Late binding means that the value of a variable used in a closure is looked up when the inner function is called, not when it was defined. This often causes unexpected results when using closures inside loops.
Code Example & explanation
- The function
f()
does not capture the value of i
, it captures a reference to the variable i
.- By the time
f()
is called (after the loop finishes), i
has the final value from the loop: 2
.- So all closures return the same value:
2
.
Solution: Use Default Arguments to Simulate Early Binding
A common and effective way to fix the late binding issue is to use default arguments. This binds the current value of the variable to the function at the time it is defined.
Code Fix & explanation
i=i
captures the current value of i
when the function is defined.- This simulates early binding, giving each function its own copy of the loop variable.
- Now each closure works as expected.
7. Real-World Use Case: Logger Factory
Imagine you are building a large application where different parts of your program need to log messages with different severity levels (INFO, DEBUG, ERROR, etc.).
Instead of writing a full class for logging, you can use a closure to create lightweight, customized loggers.
How it works:
make_logger(level)
creates a logger that remembers thelevel
it was created with.- The returned
log
function formats the message with the correct level.
Code & Exaplaination
Why this is powerful:
- No need for classes or passing
level
every time. - Keeps your code DRY (Don't Repeat Yourself).
- Easy to create multiple, independent loggers.
- Perfect for microservices, scripts, or utilities needing simple logging.
8. Closures vs Lambdas
Closures can also utilize lambda functions. A closure with a lambda can allow us to create concise functions while still capturing variables from the enclosing scope.
Code Example and explanation
- The function
power
returns a lambda function.- The lambda function takes one argument
x
and raises it to the power of n
.- Even though
power
has finished execution, the lambda
function remembers the value of n
(because it is closed over the variable n
).
When to Use Lambdas with Closures
- When you need to create a quick, unnamed function within a larger function.
- When you want to create functions that retain state from the enclosing scope but in a compact form.
- When working with higher-order functions like
map
,filter
, orreduce
, which expect a function as an argument.
9. Summary
In this section, we'll summarize the key concepts, terms, and common use cases for Python closures.
Term | Meaning |
---|---|
Closure | A closure is a function that "remembers" the environment in which it was created. Specifically, it retains access to variables from its enclosing scope even after the outer function has finished executing. |
nonlocal | The nonlocal keyword is used to modify a variable in an enclosing scope (but not global). It allows inner functions to modify variables from their enclosing functions, which is crucial for closures. |
Free Variables | These are variables used in a function that are not defined within the function itself. In closures, these free variables are bound to the function’s environment when the closure is created. |
Use Cases
Closures allow you to create functions that maintain state between function calls, without using a class or object. For example, a counter function can retain its state using closures.
Closures can be used to generate multiple functions from a single function definition. These are often referred to as factory functions, where the outer function returns a function customized with parameters.
Closures can be passed around as callback functions to handle asynchronous tasks, event handling, etc. This is particularly useful when dealing with tasks where you need to retain a context or state when the callback is executed later.
Closures are essential for decorators, which allow you to modify the behavior of a function without changing its code. A decorator is simply a function that wraps another function, and it often relies on closures to preserve state or modify behavior.
Closures can be used to encapsulate data and functions. This is especially useful when you want to hide or protect data from being accessed or modified directly from outside the scope. For example, you can create "private" variables that can only be accessed through specific methods.
Practice Exercises
Objective: Use a closure to generate functions that multiply by a given number.
-
multiplier(n)
returns the function multiply
, which remembers the value of n
.- This is a classic use of closures as function factories.
-
double
and triple
are each closures with their own copy of n
.
Objective: Use a closure to cache results of expensive computations.
- The outer function
memoize()
creates a private cache
dictionary.- The inner function
get()
checks if a result is already cached.- If not, it runs
compute()
and stores the result.- The inner function keeps a reference to
cache
via the closure.