Codectionary / Developer documentation / Python

Closures

A closure is a function that remembers and has access to variables from its enclosing scope, even after that outer function has finished executing. This happens when an inner function is defined inside an outer function and references the outer function's variables, then gets returned or passed elsewhere. Closures are the mechanism behind function factories and are commonly used to create functions with private, persistent state.

Syntax

def outer():\n    x = value\n    def inner():\n        return x\n    return inner

Examples

Basic Closure

An inner function that 'remembers' a variable from its enclosing function.

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

hello_greeter = make_greeter("Hello")
hey_greeter = make_greeter("Hey")

print(hello_greeter("Fola"))  # Hello, Fola!
print(hey_greeter("Zain"))    # Hey, Zain!

Closure with Persistent State (Counter)

Using a closure to maintain private state between calls, without using a class.

def make_counter():
    count = 0
    def increment():
        nonlocal count  # allows modifying the outer variable
        count += 1
        return count
    return increment

counter = make_counter()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

another_counter = make_counter()
print(another_counter())  # 1 (completely independent state)

Practical Example: Configurable Validators

Using a closure to build reusable, pre-configured validation functions.

def make_range_validator(minimum, maximum):
    def validate(value):
        return minimum <= value <= maximum
    return validate

is_valid_age = make_range_validator(0, 120)
is_valid_percentage = make_range_validator(0, 100)

print(is_valid_age(25))         # True
print(is_valid_percentage(150)) # False

Best practices

  • Use the 'nonlocal' keyword when an inner function needs to modify (not just read) a variable from its enclosing scope
  • Use closures for lightweight cases of remembered state (a counter, a configured function) instead of writing a full class when it fits
  • Each call to the outer function creates a fresh, independent closure - variables are not shared between separately created closures
  • Prefer a class with __call__ over a closure once the amount of internal state or behavior grows beyond one or two variables

At a glance

Purpose
Scripting and general-purpose applications
File extension
.py
Runs in
Python interpreter
Usually used with
Python standard library and packages

Specifications & further reading

Related Python documentation

def
The def keyword in Python is used to define functions - reusable blocks of code that perform specific tasks. Functions are fundamental to organizing code, avoiding repetition, and making programs more maintainable. A function can accept inputs (parameters), perform operations, and return outputs. Functions are one of the core building blocks of clean, modular Python code.
lambda
A lambda is a small, anonymous, single-expression function, created without the def keyword or a name. Lambdas are restricted to a single expression whose result is automatically returned - they can't contain multiple statements or assignments. They're most useful as short, throwaway functions passed as arguments to other functions like sorted(), map(), and filter().
return
The return statement exits a function immediately and optionally sends a value back to the caller. A function can return any type - including multiple values as a tuple - or nothing at all, in which case it implicitly returns None. Once return executes, no further code in the function runs, making it useful for early exits as well as producing a final result.
*args and **kwargs
*args and **kwargs let a function accept a variable number of arguments. *args collects any extra positional arguments into a tuple, while **kwargs collects extra keyword arguments into a dictionary. The names 'args' and 'kwargs' are just convention - what matters is the * and ** prefixes. This pattern is common in wrapper functions, decorators, and APIs that need to stay flexible about their inputs.