Closures
Nested functions
Section titled “Nested functions”Python allows you to define a function inside another function. The inner function has access to variables in the enclosing (outer) function’s scope.
def outer(): message = "Hello from outer"
def inner(): print(message) # accesses the enclosing scope
inner()
outer() # Hello from outerFree variables and closures
Section titled “Free variables and closures”When an inner function references a variable from its enclosing scope and is then returned to the outside world,
Python creates a closure — the inner function “closes over” the variable and keeps it alive even after outer has finished executing.
def make_greeter(greeting: str): def greet(name: str) -> str: return f"{greeting}, {name}!" # greeting is a free variable return greet
hello = make_greeter("Hello")hi = make_greeter("Hi")
print(hello("Alice")) # Hello, Alice!print(hi("Bob")) # Hi, Bob!greeting is a free variable in greet — it is not defined inside greet and not a global; it lives in make_greeter’s local scope.
The closure keeps the value of greeting alive for as long as hello or hi exist.
You can inspect a closure’s captured variables via __closure__:
print(hello.__closure__[0].cell_contents) # HelloThe nonlocal keyword
Section titled “The nonlocal keyword”Inside a nested function, you can read a free variable without any special keyword.
But to reassign it, you need nonlocal — otherwise Python treats the name as a new local variable.
def make_counter(start: int = 0): count = start
def increment(step: int = 1) -> int: nonlocal count # tells Python: reuse the enclosing 'count' count += step return count
def reset() -> None: nonlocal count count = start
return increment, reset
inc, rst = make_counter()print(inc()) # 1print(inc()) # 2print(inc(5)) # 7rst()print(inc()) # 1Without nonlocal count, the line count += step would raise UnboundLocalError because Python sees the assignment and treats count as a local variable that has not been assigned yet.
Factory functions — a practical closure
Section titled “Factory functions — a practical closure”def make_validator(min_val: int, max_val: int): """Return a validator function for the given range.""" def validate(value: int) -> bool: return min_val <= value <= max_val
validate.__doc__ = f"Return True if value is in [{min_val}, {max_val}]." return validate
is_valid_age = make_validator(0, 120)is_valid_score = make_validator(0, 100)is_valid_port = make_validator(1, 65535)
tests = [ ("age 25", is_valid_age(25)), ("age 200", is_valid_age(200)), ("score 95", is_valid_score(95)), ("score 101", is_valid_score(101)), ("port 8080", is_valid_port(8080)), ("port 0", is_valid_port(0)),]for label, result in tests: print(f"{label}: {result}")Loading Python runtime (first run only)…