CodeRigor

FREE LESSON

Closures, snapshots and late binding

Create independent configured functions while making captured references and configuration snapshots explicit.

A configured function lets callers fix policy now and supply data later. A closure is a function with access to referenced bindings from an enclosing function. Returning the function does not discard the bindings it needs. This extends first-class functions into small reusable tools without requiring a class for every configuration object.

Python
def make_prefixer(prefix):
    def format_word(word):
        return prefix + word
    return format_word

warning = make_prefixer("warning: ")
info = make_prefixer("info: ")
print(warning("missing"))
print(info("ready"))

Each factory call creates a separate prefix binding. Calling warning again does not change info. The factory returns format_word, not the result of calling it. This separates construction-time configuration from invocation-time input. Closures can hold changing state too, but these factories intentionally retain fixed configuration and no call history.

Separate factory invocations
start / 3
Each returned function reads its own enclosing prefix binding. The call frames may return while these bindings remain available.
Separate factory invocations
Each returned function reads its own enclosing prefix binding. The call frames may return while these bindings remain available.

A closure does not capture a deep snapshot. If its enclosing binding refers to a caller-owned dictionary, later caller mutations remain visible. That can be intentional for a live view. The build instead promises a construction-time replacement table, so it copies the mapping before returning the lookup function.

Python
def make_lookup(mapping):
    snapshot = dict(mapping)
    return lambda key: snapshot.get(key, key)

settings = {"colour": "color"}
replace = make_lookup(settings)
settings["colour"] = "changed"
print(replace("colour"), replace("unknown"))

The shallow copy is sufficient because keys and values are strings. It would not detach a nested list stored as a value. A snapshot policy must say which object boundaries it copies. get(key, key) also preserves a deliberately empty replacement; using snapshot.get(key) or key would incorrectly treat "" as missing.

A second trap appears when constructing functions in a loop. Directly referenced loop variables are looked up when the returned functions run. The loop's shared binding then contains its final value. This applies to named nested functions as well as lambdas; it is a scope rule, not a lambda defect.

Late binding shares one loop binding
start / 3
Three deferred lambdas all read the same final loop value. A helper factory or evaluated default creates separate bindings instead.
Late binding shares one loop binding
Three deferred lambdas all read the same final loop value. A helper factory or evaluated default creates separate bindings instead.
Python
late = [lambda: number for number in range(3)]
bound = [lambda number=number: number for number in range(3)]
print([function() for function in late])
print([function() for function in bound])

The default expression is evaluated at function creation. A helper factory is another solution and keeps a simpler one-argument public signature for prefixers. Neither technique deep-copies an arbitrary object: binding a mutable list as a default still retains that list. Our prefixes are immutable strings, so a bound value is enough.

For practice, predict the result after changing the caller's original mapping, then test two independently configured factories. In the build, retain the returned functions, finish constructing the full collection, and only then call them; invoking each function inside the loop could hide the late-binding bug. Include empty mappings, missing keys, empty replacement strings and repeated calls with new input.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Focus on the Python nested-function examples and the lifetime of captured bindings. Extend the idea here with explicit configuration snapshots and a loop late-binding diagnosis.