backend / python core / tricky questions / 26_nonlocal_vs_global.md

nonlocal vs global

3 min read source

nonlocal vs global

The gotcha

Assigning to a name inside a function creates a local variable, even if a same-named variable exists in an enclosing or global scope. To assign to the outer one, you must declare global (module-level) or nonlocal (enclosing function).

Minimal repro

x = 0

def f():
    x = 1          # creates a NEW local x; module-level x is untouched
    print(x)       # 1

f()
print(x)           # 0  ← unchanged
def outer():
    x = 0
    def inner():
        x = 1      # NEW local in inner; outer's x untouched
    inner()
    print(x)       # 0

Reading vs writing

Reading is easy — Python walks LEGB (Local → Enclosing → Global → Built-in):

x = 10
def f():
    print(x)       # 10 — reads from global, no declaration needed

Writing implicitly creates a local. The compiler decides at compile time which names are local based on whether any assignment to that name appears anywhere in the function body. Even an assignment in unreachable code makes the name local for the whole function:

x = 10
def f():
    print(x)       # UnboundLocalError — `x` is local because of the assignment below
    if False:
        x = 99

global — assign to module-level

counter = 0

def increment():
    global counter
    counter += 1

increment()
print(counter)     # 1

Without global, counter += 1 is counter = counter + 1 → reads then assigns → local. The read fails because the local doesn’t have a value yet → UnboundLocalError.

nonlocal — assign to enclosing function scope

def make_counter():
    n = 0
    def increment():
        nonlocal n
        n += 1
        return n
    return increment

c = make_counter()
print(c(), c(), c())   # 1 2 3

Without nonlocal, the inner function creates its own n and the closure breaks. This pattern (counter, accumulator) is the classic motivation for nonlocal — added in Python 3.0.

What nonlocal does NOT reach

nonlocal only walks enclosing function scopes, not the module:

x = 10
def f():
    nonlocal x     # SyntaxError: no binding for nonlocal 'x' found
    x = 20

For module-level, use global. For enclosing function, use nonlocal. There’s no syntax to skip levels — nonlocal binds to the nearest enclosing scope that has the name.

Common pitfalls

The “I just want to read the global” mistake:

config = {"debug": True}

def f():
    config["verbose"] = True   # no `global` needed — mutating the existing dict
    config = {}                # creates a new local; original untouched

Mutation works without global. Reassignment creates a local.

Closures over a loop variable:

funcs = []
for i in range(3):
    funcs.append(lambda: i)
print([f() for f in funcs])    # [2, 2, 2] — not what you want

i is captured by reference, not value. See 02_late_binding_closures.mdnonlocal doesn’t fix this; default-argument or partial does.

Class bodies don’t behave like functions:

x = 1
class A:
    x = 2
    def f(self):
        print(x)   # 1 — class scope is NOT in LEGB

The class body is its own scope but methods can’t see it via LEGB. Use A.x or self.x.

When to use which

  • global — rarely. Module-level mutable state is usually a smell. Prefer passing values as arguments, or wrapping state in a class.
  • nonlocal — closures over local state (counters, builders, decorators that accumulate).
  • Neither — mutate a container the outer scope owns (config["x"] = 1). No declaration needed.

Interview angle

  • Q: “What’s the difference between global and nonlocal?” — module-level vs enclosing function scope.
  • Q: “Why does counter += 1 raise UnboundLocalError if there’s a global counter?” — += is read+write; the assignment makes it local; the read fails.
  • Follow-up: “Can you read a global without declaring it?” — yes; only writes need the declaration.
  • Follow-up: “What if you use nonlocal for a name not in any enclosing function?” — SyntaxError at compile time.

See 02_late_binding_closures.md, 12_comprehension_scope.md, 21_circular_imports.md.