Python Closures

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