Variable Scope and nonlocal Keywords#
Python resolves names using LEGB: Local → Enclosing → Global → Built-in. The global and nonlocal keywords opt into cross-scope assignment — understanding when they are needed prevents UnboundLocalError and accidental mutation.
How to use this page
Intermediate intro: Functions and Scope. Closures: Closures.
At a glance
| Track | Python Advanced → Advanced Functions |
| Sections | 8 major topics |
| Outline | Use the right-hand TOC to jump |
Topics: LEGB in practice · The UnboundLocalError trap · global keyword · nonlocal keyword · global vs nonlocal · Reading vs assigning · Class scope note · Best practices
- LEGB in practice
- The
UnboundLocalErrortrap globalkeywordnonlocalkeywordglobalvsnonlocal- Reading vs assigning
- Class scope note
- Best practices
LEGB in practice#
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x)
inner()
print(x)
outer()
print(x)
# local, enclosing, global
Lookup walks LEGB until name found. Assignment creates/modifies in local scope unless declared otherwise.
The UnboundLocalError trap#
count = 0
def broken():
print(count) # UnboundLocalError
count += 1 # assignment makes count LOCAL for entire function
def read_ok():
print(count) # OK — no assignment
def fixed():
global count
count += 1
Python decides scope at compile time based on assignments in the function body.
global keyword#
| Use | Avoid |
|---|---|
| Scripts, small utilities | Library code, interviews |
| Module-level singletons | Testable functions |
Prefer passing state and returning results.
nonlocal keyword#
def make_counter():
count = 0
def increment() -> int:
nonlocal count
count += 1
return count
return increment
nonlocal binds to the nearest enclosing scope that defines the name — skips local, skips global.
def outer():
x = 1
def middle():
x = 2
def inner():
nonlocal x # middle's x, not outer's
x = 3
inner()
print(x) # 3
middle()
global vs nonlocal#
global |
nonlocal |
|
|---|---|---|
| Target | Module scope | Enclosing function scope |
| Use in nested fn | Jumps to module | Jumps to outer def |
| Typical pattern | Script counters | Closures with state |
Cannot nonlocal a name that exists only at module level — use global.
Reading vs assigning#
| Operation | Needs global/nonlocal? |
|---|---|
| Read variable from outer | No |
| Assign to name | Yes (if outer scope) |
| Mutate mutable object in place | No — list.append etc. |
items = []
def add_item(x):
items.append(x) # OK — mutates global list, no global keyword
def rebind_items():
global items
items = [] # rebinding requires global
Class scope note#
Class body does not create an enclosing scope for methods — nonlocal does not refer to class attributes. Use self.attr in methods.
Best practices#
- Minimize
global/nonlocal— explicit parameters better - Use closures +
nonlocalfor small encapsulated state - Use classes when state + multiple methods grow
- Never mutate globals in library functions silently
Interview traps (quick reference)#
| Trap | What goes wrong | Safe approach |
|---|---|---|
+= without declaration |
UnboundLocalError | nonlocal/global or pass state |
nonlocal at module level |
SyntaxError | Use global |
| Assume class attrs are enclosing | Confusion in methods | Use self |
| Mutate vs rebind confusion | Unexpected global change | Know append vs = |
Mental model checklist#
- When does assignment make a name local?
- What scope does
nonlocaltarget? - Why can you append to a global list without
global? - Why doesn't
nonlocalwork in class methods?
What's next#
| Topic | Page |
|---|---|
| Closures | Closures |
| Intermediate scope | Functions and Scope |
| Decorators | Decorators |