ProgrammingPython Developer

How do closures work in Python? What are the features of accessing variables from the outer function, and how can you avoid pitfalls associated with variable mutability?

Pass interviews with Hintsage AI assistant

Answer.

Closures are a powerful mechanism in Python that allows creating functions with "remembered" data from the surrounding context.

Background

In functional languages and in Python, functions are first-class objects. A function can return a new function that 'closes over' variables from its surrounding (outer) scope.

The Problem

Developers often get confused about how exactly closed-over variables "live" inside a closure when they are read or written, how to work with them safely (especially with mutable objects).

The Solution

If an inner function uses variables from the outer function, they are automatically "remembered" — even if the outer function has completed execution. Reading a variable is straightforward, but if you need to change its value, you must use the nonlocal keyword. When working with mutable objects (lists, dicts), this is a particular risk area.

Example:

def outer(): count = 0 def inner(): nonlocal count count += 1 return count return inner counter = outer() print(counter()) # 1 print(counter()) # 2

Key Features:

  • The nested function remembers the values of the variables that were in scope at the moment of its creation.
  • To modify closed-over variables, use nonlocal (otherwise, the operation creates a local variable).
  • References to objects in a closure are retained even after exiting the outer function, which allows implementing function factories, lexical counters, and much more.

Trick Questions.

Can you modify the value of a closed-over variable inside a function without nonlocal?

No. If you attempt to assign a new value without specifying nonlocal, Python interprets this as creating a new local variable, and the old value won't be accessible outside.

Example:

def make_counter(): count = 0 def inner(): count += 1 # UnboundLocalError! return count return inner

Can you pass arguments to the outer scope through a closure?

Yes, a closure will "remember" any variables that are accessible in the outer scope, including class attributes, global variables, and so forth. However, modifying these variables requires special efforts (for instance, using nonlocal or global).

What happens with mutable objects inside a closure?

If a closed-over variable holds a reference to a mutable object, such as a list, you can modify its contents without nonlocal, but if you try to reassign that variable, nonlocal will be required.

Example:

def make_appender(): result = [] def append(x): result.append(x) # Allowed! return result return append f = make_appender() print(f(1)) # [1] print(f(2)) # [1, 2]

Typical Mistakes and Anti-patterns

  • Attempting to reassign a variable in a closure without nonlocal.
  • Using closures to store mutable state without realizing potential memory leaks.
  • Difficult-to-read code due to an excessive number of closed-over variables.

Real-life Example

Negative Case

A developer writes a closure, modifies a variable without nonlocal — an UnboundLocalError arises.

Pros:

  • Quick prototyping of counters, factories.

Cons:

  • Unpredictable behavior, bugs arising from a mistake with nonlocal.

Positive Case

Explicit usage of nonlocal for controlled state in a closure.

Pros:

  • Clearly controllable state, easy to implement counters and function factories.

Cons:

  • More challenging to understand and debug large chains of closures.