Remember
A closure can retain access to a name from an enclosing function after that function has returned.
A closure can retain access to a name from an enclosing function after that function has returned.
Follow the example, change its sample, and compare what happens.
Explain the result and test your understanding.
Focus on how the names, objects, and calculations change. We’ll inspect the printed output in the next section.
Follow the code and watch its state change. Guided simulation.
def make_adder(amount):
def add(number):
return number + amount
return add
add_five = make_adder(5)
print(add_five(3))Top-level scope
function make_adderObject 3 · functionfunction addA closure can retain access to a name from an enclosing function after that function has returned.
(no output)
What do you expect this program to print?
EXAMPLE INSTRUCTIONS
def make_adder(amount):
def add(number):
return number + amount
return add
add_five = make_adder(5)
print(add_five(3))
|
v
DISPLAYED RESULT
8
FINAL BINDINGS
make_adder --> object 1 (function)
add_five --> object 3 (function)
object 1: function make_adder
object 3: function addChoose a different sample input, then follow every step through to the output. Before revealing it, predict what the program will display.
Choose a sample and follow its execution. Guided simulation.
def make_adder(amount):
def add(number):
return number + amount
return add
add_five = make_adder(5)
print(add_five(3))Top-level scope
function make_adderObject 3 · functionfunction addThe example has finished. Compare the final output with the values and references above.
8
What do you expect this program to print?
def make_adder(amount):
def add(number):
return number + amount
return add
add_five = make_adder(5)
print(add_five(3))A closure is a nested function that captures the state of its enclosing scope—even if that scope has finished execution. This allows the inner function to remember variables from the outer function.
Closures allow you to:
Let’s break it down with an example. The closure is formed when three conditions are met:
def outer_function(msg):
def inner_function():
print(f"Message: {msg}")
return inner_function
# Create the closure
my_closure = outer_function("Hello World!")
# Now call the closure
my_closure()
Message: Hello World!
Even though outer_function has already finished executing, the variable msg still exists in memory. That's because inner_function has "closed over" the variable msg.
We can actually inspect the closure using the __closure__ attribute.
print(my_closure.__closure__)
print(my_closure.__closure__[0].cell_contents)
(<cell at 0x...: str object at 0x...>,)
Hello Closure!
This confirms that msg is stored as part of the closure environment.
Closures shine when used to create functions that remember state. Here’s a classic counter example:
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
# Create a counter function
counter1 = make_counter()
print(counter1())
print(counter1())
print(counter1())
1
2
3
The variable count is preserved between function calls thanks to the closure.
nonlocal to modify enclosed variables. Without it, Python treats them as read-only.def no_closure():
x = 5
def show():
print("Hello")
return show
fn = no_closure()
print(fn.__closure__) # Will print None
None
In this example, the inner function show() does not reference any of the outer variables. Therefore, a closure is not created.
Closures can also be created using lambda expressions, which is common in factory functions or decorators.
def multiplier(factor):
return lambda x: x * factor
double = multiplier(2)
print(double(5)) # 10
10
Closures are useful when:
Connect what you noticed in the example with the idea below.
A closure can retain access to a name from an enclosing function after that function has returned.
A nested function can never access an enclosing function’s names.
This is the misconception to avoid.Reaching the recap and completing the quizzes are separate milestones.