In Python, a free variable is a variable that is used in a function’s code block but is not defined there and is not a global variable. Instead, it is “captured” or “bound” from an enclosing function’s scope. This mechanism is the very foundation of a closure, a function object that remembers values in enclosing scopes even if those scopes are no longer present in memory.

The Anatomy of a Closure

A closure is created when a nested function references a value from its enclosing scope, and that nested function is returned to a context where the enclosing scope has ceased to exist. The closure “closes over” the free variable, preserving it for later use.

def outer_function(msg):
    # `message` is a local variable of `outer_function`
    message = msg

    def inner_function():
        # `message` is a FREE VARIABLE for `inner_function`
        print(message)

    # Return the inner function, which has captured `message`
    return inner_function

# Call the outer function, which executes and then finishes.
# Its local scope (frame) would normally be destroyed.
my_func = outer_function("Hello, Closure!")

# Yet, when we call the inner function, it still has access to `message`.
my_func()  # Output: Hello, Closure!

Here’s why this works: inner_function is not just a function; it’s a closure. It carries with it a reference to the free variable message from the scope of outer_function, even after outer_function has finished executing.

Inspecting the closure Attribute

Every function object in Python has a __closure__ attribute. For a normal function defined at the module level, this attribute is None. However, for a closure, __closure__ is a tuple of cell objects that contain references to the free variables. Each cell has a cell_contents attribute which holds the actual value.

def make_counter(initial_value=0):
    count = initial_value  # This will become a free variable

    def counter():
        nonlocal count  # Necessary to modify the free variable
        count += 1
        return count

    return counter

# Create two independent counters
counter_a = make_counter(5)
counter_b = make_counter(100)

# Inspect the closure of counter_a
print(counter_a.__closure__)  # Output: (<cell at 0x...: int object at 0x...>,)
print(counter_a.__closure__[0].cell_contents)  # Output: 5 (the initial value)

# Call the counters and see their state change
print(counter_a())  # Output: 6
print(counter_a.__closure__[0].cell_contents)  # Output: 6 (the value was updated)
print(counter_b())  # Output: 101

This introspection reveals the hidden state that the closure maintains. Each call to make_counter creates a new, separate scope for count, resulting in independent closures for counter_a and counter_b.

The Crucial Role of the nonlocal Keyword

Attempting to rebind (assign a new value to) a free variable without the nonlocal keyword leads to a common pitfall. Python interprets the assignment as the creation of a new local variable within the nested function, shadowing the intended free variable and causing an UnboundLocalError if the variable is read before the assignment.

def broken_counter():
    count = 0
    def increment():
        # Without `nonlocal`, this creates a new local `count`.
        # Trying to read it *before* assignment causes an error.
        count = count + 1  # UnboundLocalError: local variable 'count' referenced before assignment
        return count
    return increment

broken = broken_counter()
# broken()  # This would raise an UnboundLocalError

The nonlocal statement explicitly declares a variable as a free variable, allowing you to rebind it. It tells the Python interpreter to look for the variable in the nearest enclosing scope that is not global.

def working_counter():
    count = 0
    def increment():
        nonlocal count  # Declare `count` as a free variable to be rebound
        count += 1
        return count
    return increment

fixed = working_counter()
print(fixed())  # Output: 1
print(fixed())  # Output: 2

It’s critical to understand that nonlocal does not create a new variable; it only allows an existing variable from an enclosing non-global scope to be rebound.

Common Pitfalls and Best Practices

  1. Accidental Shadowing: The most common mistake is forgetting the nonlocal keyword when assigning to a free variable, which silently creates a new local variable instead of modifying the intended one. Always use nonlocal for rebinding.

  2. Cyclic References and Memory Leaks: Closures create strong references to their captured variables. If a closure is stored in a long-lived object (e.g., a class instance) and it captures a reference to that same large object, it creates a cyclic reference that can prevent garbage collection. Be mindful of what your closures capture.

  3. Overuse and Readability: While powerful, closures can make code less readable if overused or if they capture many variables from deeply nested scopes. For complex state, a class is often a more explicit and maintainable alternative. A class with a __call__ method can emulate a closure’s behavior while being more transparent about its internal state.

  4. nonlocal Scope Search: The nonlocal keyword searches enclosing scopes from the innermost outward until it finds a variable with the given name. It will not search the global scope. If no matching variable is found, a SyntaxError is raised at compile time.

def outer():
    x = "outer"
    def inner():
        def innermost():
            nonlocal x  # This correctly finds `x` in `outer`'s scope
            x = "modified"
        innermost()
        print(f"Inner: {x}")
    inner()
    print(f"Outer: {x}")

outer()
# Output:
# Inner: modified
# Outer: modified