Skip to content

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

  1. LEGB in practice
  2. The UnboundLocalError trap
  3. global keyword
  4. nonlocal keyword
  5. global vs nonlocal
  6. Reading vs assigning
  7. Class scope note
  8. 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#

total = 0

def add(n: int) -> None:
    global total
    total += n

add(5)
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.

class C:
    x = 1
    def method(self):
        # nonlocal x  # SyntaxError — no enclosing function
        self.x = 2

Best practices#

  1. Minimize global / nonlocal — explicit parameters better
  2. Use closures + nonlocal for small encapsulated state
  3. Use classes when state + multiple methods grow
  4. 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#

  1. When does assignment make a name local?
  2. What scope does nonlocal target?
  3. Why can you append to a global list without global?
  4. Why doesn't nonlocal work in class methods?

What's next#

Topic Page
Closures Closures
Intermediate scope Functions and Scope
Decorators Decorators