Introduction: Python Closures
Sometimes, a function needs to remember a value from an earlier function call. For example, a function may create a counter that keeps its previous value each time it runs. A local variable normally becomes unavailable when the function finishes.
Python provides closures for situations like this.
What Is a Closure?
Definition: A Python closure is an inner function that retains access to variables from its enclosing function, even after the enclosing function has finished running.
Suppose a function creates a counter that needs to remember its value between calls.
def create_counter():
count = 0
def counter():
return count
return counter
# Output:
Explanation: Here, counter() can still access count after create_counter() has finished. This retained access makes counter() a closure.
How Python Closures Work
To understand how Python closures work, let’s look at the outer function, inner function, captured variables and returned function step by step.
- The Outer Function
- The Inner Function
- Capturing a Variable From the Enclosing Function
- Returning the Inner Function
A closure starts with a function inside another function. The inner function can access a variable from the outer function and keep using that variable after the outer function finishes.
1. The Outer Function
The outer function is the function that contains another function. It can also create the variable that the inner function needs to remember.
def create_greeting(name):
def greet():
return f"Hello, {name}!"
return greet
Explanation: Here, create_greeting() is the outer function because it contains the greet() function. The parameter name belongs to the scope of create_greeting(). The inner greet() function uses this variable.
Output: There is no output because create_greeting() is only defined here; it has not been called.
2. The Inner Function
The inner function is the function defined inside the outer function. It can access variables from the outer function’s scope.
def create_greeting(name):
def greet():
return f"Hello, {name}!"
return greet
Explanation: Here, greet() is the inner function because it is defined inside create_greeting(). Although name is not defined inside greet(), the inner function can access it from the enclosing function’s scope.
Output: There is no output because the functions are only being defined. The inner function has not been called.
3. Capturing a Variable From the Enclosing Function
When the inner function uses a variable from the outer function, the closure retains access to that variable.
def create_greeting(name):
def greet():
return f"Hello, {name}!"
return greet
greeting = create_greeting("Alice")
print(greeting())
# Output:
Hello, Alice!
Explanation: After create_greeting() finishes, greeting still has access to name. The closure keeps that connection so greet() can use the value later.
4. Returning the Inner Function
The outer function can return the inner function so the closure can be used after the outer function finishes.
def create_greeting(name):
def greet():
return f"Hello, {name}!"
return greet
greeting = create_greeting("Alice")
print(greeting())
# Output:
Hello, Alice!
Explanation: The statement return greet returns the function itself, not the result of calling it. Therefore, greeting refers to the greet() function. When greeting() is called later, greet() can still access the name value from create_greeting(), even though create_greeting() has already finished running.
Nested Functions and Closures
Python closures use a nested function, but not every nested function is a closure. The key difference is whether the inner function retains access to a variable from the enclosing function after the outer function finishes.
- Creating a Nested Function
- Accessing Enclosing Variables
- When Does a Nested Function Become a Closure?
Let’s look at how nested functions work and when a nested function becomes a closure:
1. Creating a Nested Function
A nested function is a function defined inside another function. The inner function stays within the scope of the outer function.
def show_message():
def display():
print("Hello, Python!")
display()
show_message()
Explanation: Here, display() is a nested function because it is defined inside show_message(). The outer function calls it while it is still running.
Connection to Closures: A closure also uses a nested function. Let’s see how a nested function can access variables from its enclosing function and when this makes it a closure.
2. Accessing Enclosing Variables
A nested function can access variables created in its enclosing function.
def create_message(name):
def show_message():
print(f"Hello, {name}!")
show_message()
create_message("Alice")
# Output:
Hello, Alice!
Explanation: The show_message() function does not define name itself. It accesses name from the enclosing function create_message().
This shows how a nested function can use data from its enclosing function, which is an important part of how a closure works.
3. When Does a Nested Function Become a Closure?
A nested function becomes a closure when it retains access to a variable from its enclosing function after the enclosing function has finished running.
To use the nested function after the outer function finishes, return the inner function:
def create_message(name):
def show_message():
return f"Hello, {name}!"
return show_message
message = create_message("Alice")
print(message())
# Output:
Hello, Alice!
Explanation: After create_message() finishes, message still refers to show_message(), and the function can still access name.
That retained access to name makes show_message() a closure.
So, a nested function is simply a function defined inside another function. A closure goes one step further by retaining access to variables from its enclosing scope after the outer function has finished.
Accessing and Modifying Closure Variables
Python closures can keep access to a variable from its enclosing function. You can read that variable directly inside the closure, and you can use nonlocal when the closure needs to change its value.
Let’s look at these three steps to understand how closure variables are accessed, modified, and preserved between function calls:
- Reading a Captured Variable
- Modifying a Captured Variable With
nonlocal - Preserving State Between Function Calls
1. Reading a Captured Variable
A closure can read a variable from its enclosing function without declaring it again.
def create_greeting(name):
def greet():
return f"Hello, {name}!"
return greet
greeting = create_greeting("Alice")
print(greeting())
# Output:
Hello, Alice!
Explanation: Here, name belongs to create_greeting(), but greet() can still read it. The closure keeps access to name after the outer function finishes.
2. Modifying a Captured Variable With nonlocal
Reading a captured variable does not require nonlocal. However, a closure needs nonlocal when it must assign a new value to a variable from the enclosing function.
def create_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
counter = create_counter()
print(counter())
print(counter())
print(counter())
# Output:
1
2
3
Explanation: The nonlocal statement tells Python that count refers to the variable created in the enclosing create_counter() function. This allows counter() to change that variable.
Without nonlocal, Python treats count as a new local variable inside counter().
3. Preserving State Between Function Calls
A closure can preserve state between calls because it retains access to the variable from its enclosing function.
def create_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
counter = create_counter()
print(counter())
print(counter())
print(counter())
# Output:
1
2
3
Explanation: Each time counter() is called, it updates the same count variable and returns its new value. The closure retains access to count between calls, allowing the counter to remember its previous value.
Practical Uses of Closures
Python closures are useful when a function needs to remember data from its enclosing function. They can be used to create customized functions, keep data private and build function-based utilities.
Here are some practical ways Python closures can be used in real programs:
1. Creating Customized Functions
A closure can create a function with behavior based on a value provided earlier.
For example, a function can create different discount calculators based on a discount rate:
def create_discount_calculator(rate):
def calculate(price):
return price - (price * rate)
return calculate
student_discount = create_discount_calculator(0.10)
member_discount = create_discount_calculator(0.20)
print(student_discount(1000))
print(member_discount(1000))
# Output:
900.0
800.0
Explanation: Each closure remembers its own rate. The same calculation logic can therefore create functions with different behavior.
2. Encapsulating Data
A closure can keep data inside an outer function instead of exposing that data as a global variable.
For example, a bank account can keep its balance inside a closure:
def create_account(initial_balance):
balance = initial_balance
def get_balance():
return balance
return get_balance
account = create_account(5000)
print(account())
# Output:
5000
Explanation: The balance variable is not directly available outside create_account(). The returned function can access it, while other parts of the program cannot change it directly.
This gives the closure a simple way to keep related data private.
3. Building Function-Based Utilities
Closures can also create small utilities that remember settings and apply them whenever the returned function runs.
For example, you can create a message formatter that remembers a prefix:
def create_formatter(prefix):
def format_message(message):
return f"{prefix}: {message}"
return format_message
error_message = create_formatter("Error")
info_message = create_formatter("Info")
print(error_message("File not found"))
print(info_message("File loaded"))
# Output:
Error: File not found
Info: File loaded
Explanation: Each returned function keeps its own prefix. This lets one function create several customized utilities without repeating the formatting logic.
Closures vs Related Concepts
Closures can look similar to regular functions and decorators because all three work with functions. The difference becomes clear when you look at what each one does and how it uses data.
1. Closures vs Regular Functions
A regular function normally works with its parameters, local variables, and other variables available in its scope. Unlike a closure, it does not retain access to variables from an enclosing function after that function has finished.
def greet(name):
return f"Hello, {name}!"
print(greet("Alice"))
# Output:
Hello, Alice!
Explanation: A closure, on the other hand, is an inner function that retains access to a variable from its enclosing function once that function has finished running.
def create_greeting(name):
def greet():
return f"Hello, {name}!"
return greet
greeting = create_greeting("Alice")
print(greeting())
# Output:
Hello, Alice!
Explanation: The main difference is that greet() in the closure keeps access to name after create_greeting() has finished.
| Regular Function | Closure |
|---|---|
| Usually receives values through parameters. | Can retain access to variables from an enclosing function. |
| Does not use an enclosing function to retain closure state. | Can retain state from its enclosing function. |
| Can exist independently. | Uses an enclosing scope to capture variables. |
2. Closures vs Decorators
A closure is a function that retains access to variables from an enclosing scope. A decorator is a technique for adding or changing a function’s behavior by wrapping it with another function.
Closures often help decorators remember the function they wrap or configuration values they receive.
For example, a simple decorator can use an inner function to wrap another function:
def log_call(func):
def wrapper():
print("Function called")
return func()
return wrapper
@log_call
def greet():
print("Hello!")
greet()
# Output:
Function called
Hello!
Here, wrapper() retains access to func, which makes wrapper() a closure. The @log_call syntax applies log_call as a decorator, which replaces greet with the returned wrapper() function.
So, a closure describes how a function retains access to enclosing variables, while a decorator describes how one function can modify or extend another function’s behavior.
| Closure | Decorator |
|---|---|
| Retains access to enclosing variables. | Modifies or extends a function’s behavior. |
| Uses an enclosing scope to retain data. | Receives a function and usually returns a modified or wrapped function. |
| Can exist without a decorator. | Can use a closure to implement its behavior. |
Common Mistakes With Closures
Python closures can be confusing when a nested function uses variables from an enclosing function. Beginners often make a few common mistakes that can prevent a closure from working as expected. Here are some common mistakes to watch for:
- Forgetting to Return the Inner Function
- Confusing Local and Enclosing Variables
- Modifying an Enclosed Variable Without
nonlocal
1. Forgetting to Return the Inner Function
A closure normally needs the outer function to return the inner function so you can call it after the outer function finishes.
A common mistake is calling the inner function inside the outer function instead of returning it:
Error Example: Forgetting to Return the Inner Function
def create_greeting(name):
def greet():
return f"Hello, {name}!"
greet()
greeting = create_greeting("Alice")
print(greeting)
# Output
None
Explanation: The outer function calls greet(), but it does not return the function or its result. As a result, create_greeting() returns None.
Correct Example: Returning the Inner Function
Return the inner function when you want to use the closure later:
def create_greeting(name):
def greet():
return f"Hello, {name}!"
return greet
greeting = create_greeting("Alice")
print(greeting())
# Output:
Hello, Alice!
2. Confusing Local and Enclosing Variables
Note: This is not a coding error by itself, but beginners can sometimes confuse variables from the inner function with variables from the enclosing function.
An inner function can access a variable from its enclosing function, but a variable created inside the inner function belongs to the inner function.
Error Example: Using a Local Variable Outside Its Function
def create_greeting(name):
def greet():
message = f"Hello, {name}!"
return message
return greet
greeting = create_greeting("Alice")
print(message)
# Output:
NameError
Explanation: Here, name comes from the enclosing function, while message is local to greet(). The message variable exists only inside greet(), so trying to use it outside that function causes a NameError.
Correct Example: Accessing the Local Variable
def create_greeting(name):
def greet():
message = f"Hello, {name}!"
return message
return greet
greeting = create_greeting("Alice")
print(greeting())
# Output:
Hello, Alice!
Explanation: Here, message is used inside greet(), where it was created. The returned value can then be accessed through greeting().
Note: Always keep track of where each variable is created and which function owns it.
3. Modifying an Enclosed Variable Without nonlocal
A closure can read an enclosed variable without nonlocal. However, assigning a new value to that variable inside the inner function requires nonlocal.
Error Example: Without nonlocal
def create_counter():
count = 0
def counter():
count += 1
return count
return counter
Explanation: Python treats count as a local variable inside counter() because the function assigns a new value to it. The code therefore raises an UnboundLocalError.
Correct Example: With nonlocal
Use nonlocal to tell Python that count belongs to the enclosing function:
def create_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
counter = create_counter()
print(counter())
print(counter())
# Output:
1
2
Explanation: The nonlocal statement lets counter() modify the count variable from create_counter() instead of creating a new local variable.
Key Takeaways: Closures
Here are the key points to remember about Python closures:
- A closure is an inner function that retains access to variables from its enclosing function.
- A nested function becomes a closure when it retains access to an enclosing variable after the outer function finishes.
- Return the inner function from the outer function to use the closure later.
- Use
nonlocalwhen a closure needs to modify a variable from its enclosing function. - Closures can preserve state between function calls without using global variables.
- Closures can create customized functions, encapsulate data, and build function-based utilities.