Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A closure in Python is a dynamically generated function object that retains access to variables from its lexical scope even after the enclosing function has finished execution. It essentially binds a function to an environment consisting of one or more free variables. For a closure to exist in Python, three specific criteria must be met:
  1. Nested Function: A function must be defined inside another function.
  2. Free Variable Reference: The inner function must reference a variable declared in the outer function’s local scope.
  3. Return the Function: The outer function must return the inner function object, not the result of calling it.

Syntax and Mechanics

When the outer function is called, it creates a new instance of the inner function, binds the current state of the referenced local variables to it, and returns this newly created function object.
def outer_function(x):
    # 'x' is a local variable to outer_function
    
    def inner_function(y):
        # 'x' is a free variable within inner_function
        return x + y
        
    # Return the function object itself
    return inner_function


# The outer function executes and returns the closure
closure_instance = outer_function(10)


# The outer function's scope is now gone, but the closure retains 'x = 10'
result = closure_instance(5)  # Evaluates to 15

Internal Representation

Python implements closures using cell objects. When a nested function references a variable from an enclosing scope, Python creates a cell object to store the value. Both the outer function’s local variable and the inner function’s free variable point to this same cell object. You can inspect a closure’s internal state using the __code__ and __closure__ dunder attributes.
  • __code__.co_freevars: A tuple containing the names of the free variables.
  • __closure__: A tuple of cell objects containing the actual bound values.

# Inspecting the free variable names
print(closure_instance.__code__.co_freevars)

# Output: ('x',)


# Inspecting the value stored in the closure's cell object
print(closure_instance.__closure__[0].cell_contents)

# Output: 10

State Mutation and the nonlocal Keyword

By default, free variables captured in a closure are read-only. If you attempt to reassign a free variable inside the inner function, Python will treat it as a new local variable declaration, shadowing the outer variable and potentially raising an UnboundLocalError. To mutate the state of a captured free variable, you must explicitly declare it using the nonlocal keyword. This instructs the Python interpreter to bind the assignment to the variable in the nearest enclosing lexical scope.
def outer_mutating_function():
    state = 0
    
    def inner_mutating_function():
        nonlocal state  # Explicitly bind to the outer scope's 'state'
        state += 1
        return state
        
    return inner_mutating_function

counter = outer_mutating_function()
counter()  # Returns 1
counter()  # Returns 2

Late Binding

Python closures exhibit late binding behavior. The values of variables used in the closure are looked up at the time the inner function is called, not at the time it is defined. If the free variable changes before the closure is invoked, the closure will use the updated value.
def create_multipliers():
    multipliers = []
    for i in range(3):
        # 'i' is a free variable. Its final value will be 2.
        multipliers.append(lambda x: x * i)
    return multipliers

m0, m1, m2 = create_multipliers()


# All closures reference the same 'i', which is 2 when the loop finishes.
print(m0(10))  # Output: 20
print(m1(10))  # Output: 20
print(m2(10))  # Output: 20
Master Python with Deep Grasping Methodology!Learn More