PYTHON / CHAPTER 10
Lexical scope and closures
IntermediateTrack which binding a nested function reads or changes.
By the end of this chapter
- Distinguish rebinding from mutation
- Use nonlocal deliberately
- Avoid late-binding surprises in callbacks
The idea
A nested function can read names from its enclosing function. Assigning to a name normally creates a local binding in the assigning function, even when an outer name has the same spelling. nonlocal explicitly targets an existing enclosing-function binding. Mutating a captured list through its methods does not rebind the list’s variable and therefore follows a different rule.
Closures retain access to bindings rather than freezing every value when a function is created. Functions built in a loop can all read the loop variable’s final value when called later. A default argument can deliberately capture the current value for each function, because defaults are evaluated at definition time. Prefer a small factory when that intent would otherwise be obscure.
def counter():
value = 0
def step():
nonlocal value
value += 1
return value
return step
a, b = counter(), counter()
print(a(), a(), b())1 2 1
Each factory call creates a separate enclosing value; calls through a share one of them.
Try it yourself
Create a counter factory with a configurable starting value and independent state per factory call.
Use your own editor for this exercise. Runnable challenges on CodeQuest are available for JavaScript, Python, and SQL.
Open Python coding challengesCHECK YOUR UNDERSTANDING