Python Closures Explained: Free Variables, nonlocal, and Examples

Python Closures Explained: Free Variables, nonlocal, and Examples — cover image

Key Takeaways

A closure is a function that, even after the outer function has completed its execution, retains values from its enclosing scope.

A nested function becomes a Python closure only when it captures one or more free variables from its enclosing function.

Closures rely on Python's lexical scoping rules to preserve access to captured variables.

Closures can store state, making them useful for building function factories and lightweight stateful functions.

Comprehending closures facilitates the explanation of sophisticated Python features like decorators and typical issues like late binding.

Introduction

Let's say a function yields another function. The local variables of the outer function should vanish after it is finished. However, the returning function continues to access those variables as if nothing had changed when you call it later.

How is that possible?

The answer lies in closures, one of Python's most powerful features for working with functions. With the use of a closure, a function can continue to access variables from its surrounding scope even after that scope has vanished. This behavior enables functions to preserve state, create specialised functions, and form the foundation of features like decorators.

Understanding what is closure in Python requires more than memorising a definition. It involves understanding how nested functions, scopes, and object references work together. Once these concepts become clear, closures become much easier to reason about.

By the end of this blog, you'll understand what a closure is, how Python creates closures, why they matter, and how they allow functions to retain state beyond the lifetime of their enclosing function.

What is Closure?

A closure is a function which, even after the enclosing function has completed its execution, retains and can access variables from its enclosing scope.

In Python, functions are first-class objects; they can be created inside other functions, passed as arguments, returned from functions, and assigned to variables. When an inner function retains access to variables defined in its enclosing function, it forms a closure.

In simple terms:

A closure is an inner function together with the variables it captures from its enclosing scope.

The captured variables are preserved because the returned function maintains a reference to them, allowing the function to continue using those values whenever it is called.

Understanding what is closure is important because it explains how Python enables functions to carry information beyond the lifetime of the function that created them.

Why Closures Exist

Consider a situation where you want a function to remember some information between calls without relying on global variables.

For example, suppose you want to create one function that always multiplies numbers by 2 and another that always multiplies numbers by 5. Rather than writing separate functions for every multiplier, you can write one function that creates customized multiplier functions.

For this to work, the returned function must remember the multiplier that was originally provided.

This is exactly why closures exist.

Instead of copying values into the returned function, Python allows the inner function to retain access to variables from its enclosing scope. These captured variables remain available even after the outer function completes execution.

A simple method for maintaining state inside functions while keeping it private is to use closures. They are therefore helpful for encapsulating data, developing customized methods, and avoiding needless global variables.

Understanding Nested Functions

A function written within another function is known as a nested function.

Example:

def outer():
    def inner():
        print("Hello")

    inner()

outer()

In this case, inner() is limited to outer(). Direct calls to it from outside the enclosing function are not possible.

Nested functions are useful because they allow related functionality to be grouped together and enable the inner function to access names defined in the enclosing function.

For example:

def outer():
    message = "Hello"

    def inner():
        print(message)

    inner()

outer()

The inner function can access message because it is defined in the enclosing scope.

However, a nested function is not automatically a closure.

A nested function becomes a closure only when:

  • it references one or more variables from its enclosing function, and
  • it continues to access those variables after the outer function has returned.

This distinction is important because every closure is a nested function, but not every nested function forms a closure.

What Makes a Python Closure?

A Python closure is created only when three conditions are met:

  • An outer function defines one or more local variables.
  • An inner function references one or more of those variables.
  • The outer function returns the inner function.

Consider this closure example:

def greeting(text):
    def greet(name):
        return f"{text}, {name}!"

    return greet

Here:

greeting() is the outer function.

greet() is the inner function.

text is a free variable because it belongs to the enclosing function but is used inside the inner function.

Returning greet creates a closure.

Even after greeting() finishes execution, the returned function still remembers the value of text.

This ability to preserve access to variables from an enclosing scope is what distinguishes a Python closure from an ordinary nested function. It combines functions, lexical scope, and object references to create functions that carry their own state wherever they are used.

How Closures Capture Variables

A closure is more than a nested function. Its defining feature is that it captures variables from its enclosing scope and continues to use them even after the enclosing function has finished executing.

A common misconception is that a closure copies the values of those variables. It doesn't.

Instead, a closure retains references to the variables defined in the enclosing scope. As long as the closure exists, Python keeps those variables alive, allowing the inner function to access them whenever it is called.

This behavior is what enables closures to preserve state and makes them useful for creating specialized functions.

Free Variables

A free variable is a variable that is used inside a function but defined in its enclosing function, rather than within the function itself.

Consider this example:

def outer():
    message = "Welcome"

    def inner():
        print(message)

    return inner

In this example:

message is created inside outer().

inner() uses message but does not define it.

Therefore, message is a free variable for inner().

Free variables are the foundation of closures. Without them, an inner function has no state to capture, and no closure is formed.

Lexical Scoping

Python determines where a function looks for names based on where the function is defined, not where it is called. This behavior is known as lexical scoping (also called static scoping).

When Python encounters a name inside a function, it searches according to the LEGB rule:

  • Local scope
  • Enclosing scope
  • Global scope
  • Built-in scope

For a closure, the enclosing scope is especially important.

def outer():
    language = "Python"

    def inner():
        print(language)

    return inner

Here, language is not a local variable of inner(). During name resolution, Python doesn't find it in the local scope, so it searches the enclosing scope and finds language inside outer().

Because of lexical scoping, the inner function always remembers the environment where it was defined, regardless of where it is called later.

Remembering Data After the Outer Function Returns

One of the most surprising aspects of closures is that they continue to access variables even after the outer function has completed execution.

Normally, local variables exist only while a function is running. Once the function returns, its local namespace is destroyed.

Closures are different.

When an outer function returns an inner function that references free variables, Python preserves those variables instead of discarding them. The closure keeps references to the captured variables, allowing the inner function to continue using them.

For example:

def outer():
    message = "Hello"

    def inner():
        return message

    return inner

greet = outer()

print(greet())
Output
Hello

Although outer() has already finished executing, greet() still accesses message.

The variable survives because the closure still references it, not because the outer function is still running.

This ability to preserve data beyond the lifetime of a function is what makes closures unique.

Closure Example in Python

Let's see how a closure example works step by step.

def multiplier(factor):
    def multiply(number):
        return number * factor

    return multiply

double = multiplier(2)

print(double(5))
Output
10

Here's what happens during execution:

multiplier(2) is called.

The local variable factor is assigned the value 2.

Python creates the inner function multiply().

multiply() references factor, making it a free variable.

multiplier() returns the inner function.

Even though multiplier() has finished executing, the returned function retains access to factor.

Calling double(5) multiplies 5 by the remembered value 2.

Notice that the returned function doesn't receive factor as an argument. Instead, it retrieves the value from its captured enclosing scope.

This is the defining characteristic of a closure, it combines a function with the variables it remembers.

Inspecting a Closure (__closure__ and cell_contents)

Python allows you to inspect the variables captured by a closure through the __closure__ attribute.

Using the previous example:

def multiplier(factor):
    def multiply(number):
        return number * factor

    return multiply

double = multiplier(2)

print(double.__closure__)

If we want to check what is the closure for a function, we use the '__closure__' attribute that gives us a set of memory cell objects. Each cell object is holding one of the captured variable values.

To view the actual value stored inside a cell, use the cell_contents attribute.

print(double.__closure__[0].cell_contents)
Output
2

Here:

The variables that the closure has captured are contained in __closure__.

cell_contents retrieves the value stored in each captured variable.

Inspecting these attributes helps you understand that a closure doesn't simply remember values conceptually, it actually stores references to the variables it captures. This mechanism enables a closure to preserve state long after its enclosing function has returned.

Using nonlocal with Closures

A closure can read variables from its enclosing scope without any extra syntax. However, reading a variable and modifying it are two different operations.

If an inner function only accesses an enclosing variable, Python automatically finds it using lexical scoping. But if the inner function tries to assign a new value to that variable, Python treats it as a new local variable unless instructed otherwise.

By indicating to Python that a variable is part of the closest enclosing function, the nonlocal keyword enables the inner function to change an existing variable rather than create a new one.

Example:

def counter():
    count = 0

    def increment():
        nonlocal count
        count += 1
        return count

    return increment

next_count = counter()

print(next_count())
print(next_count())
Output
1
2

Here, count belongs to the enclosing function counter(). Using nonlocal allows increment() to update the same variable each time it is called.

Without nonlocal, the assignment would create a local variable named count, and the closure would no longer modify the captured variable.

Why Rebinding Requires nonlocal

Why Rebinding Requires nonlocal

One of Python's scoping rules is that assignment creates a local variable unless stated otherwise.

Consider this example:

def counter():
    count = 0

    def increment():
        count += 1
        return count

    return increment

This code raises an error because the assignment count += 1 tells Python that count is a local variable inside increment(). However, Python attempts to read the variable before it has been assigned, resulting in an UnboundLocalError.

The correct approach is:

def counter():
    count = 0

    def increment():
        nonlocal count
        count += 1
        return count

    return increment

The nonlocal declaration changes the binding behavior. Instead of creating a new local variable, Python updates the existing variable from the enclosing function.

Use nonlocal only when an inner function needs to rebind an enclosing variable. If the variable is only being read, no declaration is required.

How Closures Store State

One of the biggest advantages of closures is their ability to store state between function calls.

Normally, local variables disappear after a function returns. With a closure, captured variables remain available because the returned function continues to reference them.

Consider this example:

def create_counter():
    count = 0

    def increment():
        nonlocal count
        count += 1
        return count

    return increment

counter = create_counter()

print(counter())
print(counter())
print(counter())
Output
1
2
3

Although create_counter() executes only once, the closure remembers the value of count across multiple calls.

The state belongs to the closure itself, not to the outer function. Each time create_counter() is called, Python creates a new closure with its own independent state.

counter1 = create_counter()
counter2 = create_counter()

print(counter1())
print(counter1())

print(counter2())
Output
1
2
1

Every closure keeps a copy of the captured state for itself.

Function Factories

A function that generates and returns other functions is called a function factory.

Because the returning function retains the values given when it was constructed, closures enable function factories.

Example:

def power(exponent):
    def calculate(base):
        return base ** exponent

    return calculate

square = power(2)
cube = power(3)

print(square(4))
print(cube(4))
Output
16
64

Here's what happens:

power(2) creates a function that always squares numbers.

power(3) creates another function that always cubes numbers.

Each returned function remembers its own value of exponent.

Without closures, you would need to repeatedly pass the exponent every time you call the function. Closures eliminate this repetition by preserving the required data automatically.

Function factories are a practical application of closures because they allow you to generate customized functions without duplicating code.

Closures and Decorators

Closures are the foundation of Python decorators.

A decorator being a function that takes a different function, generates an inner function, and then gives that inner function back. The returned function remembers objects from its enclosing scope, making it a closure.

A simplified example looks like this:

def decorator(func):
    def wrapper():
        print("Before function")
        func()
    return wrapper

In this example:

wrapper() is the inner function.

func is a free variable captured from the enclosing function.

Returning wrapper() creates a closure.

Although decorators introduce additional concepts, they rely on the same mechanism that powers closures: an inner function remembering variables from its enclosing scope.

For this reason, understanding closures first makes decorators much easier to learn. Almost every Python decorator uses closures to preserve information between the time the decorator is applied and the time the decorated function is eventually called.

The Late Binding Pitfall

Closures capture variables, not the values those variables hold at the time the closure is created. This behavior is known as late binding, and it is one of the most common reasons beginners find closures confusing.

Consider the following example:

functions = []

for i in range(3):
    def show():
        print(i)

    functions.append(show)

for function in functions:
    function()
Output
2
2
2

At first glance, you might expect the output to be:

0

1

2

However, all three functions print 2.

This happens because each closure references the same variable i, not the value of i during each iteration. By the time the functions are called, the loop has completed, and i holds its final value, 2.

One common solution is to bind the current value as a default argument.

functions = []

for i in range(3):
    def show(value=i):
        print(value)

    functions.append(show)

for function in functions:
    function()
Output
0
1
2

Understanding late binding is essential because it explains why closures sometimes behave differently from what new Python programmers expect.

Closures vs Regular Functions

Every closure is a function, but not every function is a closure.

A regular function performs its task using local variables, parameters, or global names. Once it finishes execution, its local variables are discarded.

A closure, on the other hand, carries references to variables from its enclosing scope, allowing it to retain state even after the outer function has returned.

Regular Function Closure
Executes independently Retains variables from an enclosing scope
Local variables disappear after execution Captured variables remain available
Does not preserve state between calls Can preserve state across calls
Suitable for general-purpose tasks Suitable for stateful or customized behavior

If a function does not capture free variables from an enclosing scope, it is simply a regular function rather than a closure.

Closures vs Classes

Closures and classes can both preserve state, but they solve the problem differently.

A closure stores state through captured variables, whereas a class stores state in instance attributes.

Closure Class
Stores state using captured variables Stores state using object attributes
Lightweight and concise Better for complex state and behavior
Ideal for a single operation Suitable for multiple related operations
Doesn't require creating a class Organizes large programs more effectively

Choose a closure when you need a small amount of state for a single function.

Choose a class when multiple methods need to share and modify the same state or when the logic becomes more complex.

When Closures Are Useful

When Closures Are Useful

Closures are particularly useful when a function needs to remember information without exposing that information as a global variable.

Some common use cases include:

  • Creating specialized functions through function factories.
  • Maintaining state across several function calls.
  • Encapsulating implementation details inside a function.
  • Building decorators that wrap and extend existing functions.
  • Reducing reliance on global variables.

Closures provide a simple way to combine behavior and state while keeping the implementation private and reusable.

Best Practices

Following a few simple practices can make closures easier to understand and maintain.

Capture Only What You Need

Capture only the variables required by the inner function. Avoid unnecessarily retaining objects that are no longer needed.

Use nonlocal Only When Required

Use nonlocal only if the inner function needs to modify an enclosing variable. Reading an enclosing variable does not require it.

Keep Closures Focused

For your closures to be most effective, limit them to performing a single task clearly and thoroughly. It is hard with closures that do many different things at the same time, to figure out what's going on or find out where a problem is coming from.

Prefer Classes for Complex State

When a closure needs to deal with several variables or different actions, you can expect that a class will make things easier and keep the overall structure of your code stable.

Watch for Late Binding

Whenever you create a closure within a loop, take into account that the closure will hold a reference to the variable rather than a copy of the current value of the variable. That way, if it should be closure different closures which each remember their own values, that variable should be bound first before the closure is created.

Common Mistakes

Mistaking Closures for Nested Functions

Of course every closure is a nested function, but a nested function only becomes a closure when it has captured some of the variables of the surrounding scope.

Assuming Closures Copy Values

Copies of variables are not kept in closures. In the enclosing scope, they keep references to the original variables.

Forgetting nonlocal

When rebinding a captured variable, omitting the nonlocal keyword causes Python to create a new local variable instead of modifying the existing one.

Ignoring the Late Binding Behavior

Closures created inside loops often produce unexpected results because all of them reference the same loop variable.

Using Closures for Large Applications

Closures are excellent for lightweight state management. For applications requiring multiple operations or extensive shared state, classes are usually easier to understand and maintain.

Summary

A closure function is just an inner function that takes the outer function scope and keeps it. You will remember this as what Closure means here in Python, with you understanding how, through lexical scope, they capture free variables, the reason nonlocal needs to be used for rebinding, and closures retaining state. You have also gone through function factories, how closures compare to decorators, and the late binding mistake. Besides closures vs classes, there are also other considerations when deciding how to best structure your program! Learning these things will enable you to better see how Python joins functions, scope, and instance references into a mechanism for developing flexible and reuseful code that is powerful enough.

Frequently Asked Questions

What is closure in Python?

A closure is an inner function that remembers and accesses variables from its enclosing scope even after the outer function has returned.

What is the difference between a nested function and a closure?

A function placed within another function is called a nested function. Only when it catches one or more free variables from its surrounding scope does it become a closure.

What is a free variable?

A free variable is a variable that is used inside a function but defined in its enclosing function rather than within the function itself.

Why do closures use the nonlocal keyword?

Instead of generating a new local variable, the nonlocal keyword enables an inner function to change a variable from its outer function.

How do closures store state?

Closures allow captured variables to be accessible during several function calls by keeping references to those variables.

What is the late binding problem?

Late binding occurs because closures capture variables rather than their values. If the variable changes later, the closure uses its most recent value.

When should I use a closure instead of a class?

Use a closure for small, focused tasks that require a little state. Choose a class when managing complex state or multiple related behaviors.

How are closures related to decorators?

Decorators use closures to remember the function being decorated and any additional data needed when the decorated function is called.