Worked examples — global and nonlocal keywords
This is the worked-examples companion to global and nonlocal keywords. The parent note taught you the two rules:
- Assigning a name anywhere in a function makes it local for the whole function body (decided at compile time).
globalreaches the module top level;nonlocalreaches the nearest enclosing function.
Here we do nothing but run every possible case so no scenario surprises you. If a word below is unfamiliar (scope, closure, rebind), the parent note and Scope and LEGB rule define it — we assume only those.
The scenario matrix
Every situation this topic can throw at you is one cell of this table. The examples that follow are each tagged with the cell(s) they cover, and together they fill it completely.
| # | Case class | Do you need a keyword? | Which one? |
|---|---|---|---|
| C1 | Read-only outer variable | No | — |
| C2 | Rebind a module variable | Yes | global |
| C3 | Rebind an enclosing-function variable | Yes | nonlocal |
| C4 | Mutate an object (list/dict) — no = on the name |
No | — |
| C5 | Degenerate: rebind before reading, no keyword | Crashes | UnboundLocalError |
| C6 | Wrong keyword: global where you needed nonlocal |
Crashes / misses | SyntaxError or wrong var |
| C7 | Zero enclosing scopes: nonlocal at module top / one level |
Crashes | SyntaxError |
| C8 | Deep nesting — nonlocal picks nearest enclosing only |
Yes | nonlocal (nearest) |
| C9 | Real-world word problem (shared counter / config) | Yes | global |
| C10 | Exam twist: global creates a module var that didn't exist |
Yes | global |

Example 1 — Read-only outer variable (C1)
Example 2 — The classic crash (C5)
Example 3 — Rebind a module variable (C2)
Example 4 — Mutate, don't rebind (C4)
Example 5 — Closure with nonlocal (C3)
def make_counter():
n = 0
def step():
nonlocal n
n += 1
return n
return step
c = make_counter()
print(c(), c(), c())Forecast: 1 2 3, or does each call reset to 1?
Step 1 — Where does n live?
Why this step? n = 0 is in make_counter, the enclosing function of step — not the module. So the target scope is E, not G.
Step 2 — step rebinds n (n += 1 is n = n + 1).
Why this step? += on a name is a rebind. Without nonlocal, step would make a fresh local n and crash reading it (a C5 inside a closure). nonlocal n points the rebind at the enclosing n.
Step 3 — The closure keeps n alive.
Why this step? Even after make_counter returns, step (a closure) holds a reference to n, so it survives across calls: 0→1→2→3.
Verify: Prints 1 2 3.
Example 6 — Wrong keyword: global for an enclosing var (C6)
def make_counter():
n = 0
def step():
global n # wrong!
n += 1
return n
return step
c = make_counter()
print(c())Forecast: Same 1 as Example 5, or something breaks?
Step 1 — global n looks at the MODULE, not the enclosing function.
Why this step? global always reaches the ground floor. The enclosing n = 0 is invisible to it.
Step 2 — n += 1 reads a module n that doesn't exist.
Why this step? There is no module-level n, so n + 1 raises NameError: name 'n' is not defined.
Verify: Raises NameError. Rule: enclosing-function var ⇒ nonlocal, never global.
Example 7 — nonlocal with no enclosing scope (C7)
def solo():
nonlocal y # no enclosing function above solo
y = 5Forecast: Runtime crash, or does Python reject it before running?
Step 1 — nonlocal demands an existing binding in an enclosing function.
Why this step? solo is at the module top level; there is no enclosing function holding a y. nonlocal has no target.
Step 2 — This is caught at compile time.
Why this step? Unlike UnboundLocalError (a runtime error), a bad nonlocal is a SyntaxError: no binding for nonlocal 'y' found — Python won't even start.
Verify: Raises SyntaxError at definition time.
Example 8 — Deep nesting: nonlocal picks the nearest enclosing (C8)
def a():
x = "a"
def b():
x = "b"
def c():
nonlocal x
x = "c"
c()
return x
return b(), x
print(a())Forecast: Does c change b's x, a's x, or both?
Step 1 — nonlocal binds to the NEAREST enclosing scope that has x.
Why this step? Walking outward from c: b has x = "b" — that's the nearest. c rebinds that one, never a's.
Step 2 — Trace the values.
Why this step? c() sets b's x to "c". So b returns "c". a's own x stays "a", untouched.
Verify: Prints ('c', 'a').

Example 9 — Real-world: shared config toggle (C9)
A logging module has a switch. Turning it on from anywhere should affect the whole program.
DEBUG = False
def enable_debug():
global DEBUG
DEBUG = True
def log(msg):
if DEBUG:
print("LOG:", msg)
log("first")
enable_debug()
log("second")Forecast: How many lines print?
Step 1 — log only reads DEBUG (C1): no keyword.
Why this step? No = targets DEBUG in log; reading climbs to the module value.
Step 2 — enable_debug rebinds DEBUG (C2): needs global.
Why this step? DEBUG = True is a rebind of the module switch. global DEBUG makes it hit the shared variable, not a throwaway local.
Step 3 — Trace.
Why this step? log("first") → DEBUG is False → nothing. enable_debug() → module DEBUG = True. log("second") → prints LOG: second.
Verify: Exactly one line prints: LOG: second. (This is a deliberate global side effect — not a pure function.)
Example 10 — Exam twist: global creates a new module variable (C10)
def init():
global created
created = 99
init()
print(created)Forecast: created was never defined at module level before init ran — NameError?
Step 1 — global created names a module variable that may not exist yet.
Why this step? global doesn't require the variable to pre-exist. It says "the name created refers to module scope."
Step 2 — The assignment creates it there.
Why this step? created = 99 binds a new module-level name. After init() runs, module scope now has created = 99.
Verify: Prints 99. Note: it exists only after init() is called — calling print(created) before init() would raise NameError.
Recall Which cell is this? Quick self-test
total = 0
def add(v):
total += v
add(5)Answer ::: Cell C5 — total += v rebinds total, making it local; the RHS reads it before assignment → UnboundLocalError. Fix with global total.
See a = (or +=) on the name? → you're rebinding → pick global (module) or nonlocal (enclosing). Just calling .append() / reading? → keyword needed: none.
Connections
- global and nonlocal keywords
- Scope and LEGB rule
- Functions in Python
- Closures
- Mutable vs Immutable objects
- UnboundLocalError
- Pure functions and side effects