Python Closures

Let’s look at closures

  1. Notice

    A closure can retain access to a name from an enclosing function after that function has returned.

  2. Try

    Follow the example, change its sample, and compare what happens.

  3. Check

    Explain the result and test your understanding.

Watch how it works

Focus on how the names, objects, and calculations change. We’ll inspect the printed output in the next section.

Explore the idea

Follow the code and watch its state change. Guided simulation.

Code
Python
def make_adder(amount):
    def add(number):
        return number + amount
    return add
add_five = make_adder(5)
print(add_five(3))
Live state

Top-level scope

make_adder→Object 1
add_five→Object 3
Object 1 · functionfunction make_adderObject 3 · functionfunction add
This step

A closure can retain access to a name from an enclosing function after that function has returned.

Picture closures

A closure can retain access to a name from an enclosing function after that function has returned.

Image brief

Alt text
A diagram for Python Closures. Instructions: def make_adder(amount): def add(number): return number + amount return add add_five = make_adder(5) print(add_five(3)). Displayed result: 8. The final example bindings are make_adder --> object 1 (function); add_five --> object 3 (function). object 1: function make_adder; object 3: function add
Image generation prompt
Create a clean educational illustration for a Python programming lesson titled "Python Closures". Teaching focus: A closure can retain access to a name from an enclosing function after that function has returned. Depict exactly the relationships and labels in this reference diagram: 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 add Use a flat vector style, generous spacing, clear arrows and legible labels, with a restrained blue/teal palette on a plain background. The diagram must explain the programming idea accurately. Do not depict variables as boxes containing copied objects when they share a reference. Avoid mascots, decorative screenshots, logos, gradients and extra text. Landscape composition, approximately 3:2, suitable for a course card. Accessible description: A diagram for Python Closures. Instructions: def make_adder(amount): def add(number): return number + amount return add add_five = make_adder(5) print(add_five(3)). Displayed result: 8. The final example bindings are make_adder --> object 1 (function); add_five --> object 3 (function). object 1: function make_adder; object 3: function add

Follow the execution

Choose a different sample input, then follow every step through to the output. Before revealing it, predict what the program will display.

Execution workspace

Choose a sample and follow its execution. Guided simulation.

Code
Python
def make_adder(amount):
    def add(number):
        return number + amount
    return add
add_five = make_adder(5)
print(add_five(3))
Live state

Top-level scope

make_adder→Object 1
add_five→Object 3
Object 1 · functionfunction make_adderObject 3 · functionfunction add
This step

The example has finished. Compare the final output with the values and references above.

Terminal
Output
8

What do you expect this program to print?

What does this example display?

def make_adder(amount):
    def add(number):
        return number + amount
    return add
add_five = make_adder(5)
print(add_five(3))
What does this example display?
Choose one answer. You can try again.

Answer and explanation

8

Follow the printed values in order. The output is: 8

Which explanation matches what you learned?

Which explanation matches what you learned?
Choose one answer. You can try again.

Answer and explanation

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.

What is a Closure in Python?

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.

Why Do Closures Matter?

Closures allow you to:

  • Preserve data across function calls without using global variables
  • Write more concise and elegant code
  • Implement decorators and function factories

How to Create a Closure

Let’s break it down with an example. The closure is formed when three conditions are met:

  1. There is a nested function
  2. The nested function references variables from the outer function
  3. The outer function returns the nested function
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!

What Just Happened?

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.

Checking for Closure

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.

Real-World Use Case: Counter Generator

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.

Common Pitfalls and Checks

  • Don’t expect the outer function's variables to persist unless they are referenced in the inner function.
  • Use nonlocal to modify enclosed variables. Without it, Python treats them as read-only.
  • A closure is not created if the inner function doesn’t reference outer variables.
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 vs. Lambda Functions

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

Recap: When to Use Closures

Closures are useful when:

  • You want to keep some data private (function-level encapsulation)
  • You need a factory function that generates other functions with customized behavior
  • You’re building decorators or callback systems

What to remember

Connect what you noticed in the example with the idea below.

Remember

A closure can retain access to a name from an enclosing function after that function has returned.

Watch out for this idea

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.

    Next lesson: Python Decorators →