Python Basics — global & nonlocal

3 minute read

global & nonlocal

Python provides two keywords — global and nonlocal — that let you modify variables in outer scopes. While useful in specific situations, they should be used sparingly.

The global Keyword

By default, assigning to a variable inside a function creates a local variable. The global keyword tells Python to use the module-level variable instead:

count = 0

def increment():
    global count       # tells Python: use the global count
    count += 1

increment()
increment()
increment()
print(count)  # 3

Without global:

count = 0

def increment():
    count += 1     # UnboundLocalError!

# increment()  # Error

Declaring Multiple Globals

x = 0
y = 0

def reset():
    global x, y
    x = 0
    y = 0

Risks of global

# ❌ Hard to track — who changed this?
settings = {"debug": False}

def enable_debug():
    global settings
    settings["debug"] = True

def disable_debug():
    global settings
    settings["debug"] = False

# Scattered global modifications make code hard to debug
Risk Example
Hidden dependencies Function behavior depends on external state
Hard to test Must set up global state before testing
Race conditions Multiple threads modifying the same global
Unexpected side effects Calling a function changes things elsewhere

The nonlocal Keyword

nonlocal does for enclosing function scopes what global does for module scope:

def outer():
    count = 0

    def inner():
        nonlocal count    # modify the enclosing scope variable
        count += 1

    inner()
    inner()
    inner()
    print(count)  # 3

outer()

Without nonlocal:

def outer():
    count = 0

    def inner():
        count += 1    # UnboundLocalError!

    inner()

# outer()  # Error

nonlocal with Multiple Nested Levels

nonlocal finds the nearest enclosing scope that has the variable:

def level1():
    x = "level1"

    def level2():
        x = "level2"

        def level3():
            nonlocal x      # binds to level2's x (nearest)
            x = "modified"

        level3()
        print(f"level2: {x}")   # modified

    level2()
    print(f"level1: {x}")       # level1 — unaffected

level1()
# Output:
# level2: modified
# level1: level1

When to Use (and When to Avoid)

Acceptable Uses

Counters in closures:

def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

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

Simple flag toggles:

def make_toggle():
    state = False
    def toggle():
        nonlocal state
        state = not state
        return state
    return toggle

switch = make_toggle()
print(switch())  # True
print(switch())  # False

When to Avoid

Instead of Use
global for sharing state Pass arguments and return values
global for configuration Use a config dictionary or class
nonlocal for complex state Use a class with attributes
Mutable global variables Constants (UPPER_CASE) that don’t change

Alternatives to global

Return Values (Preferred)

# ❌ Using global
total = 0

def add_global(n):
    global total
    total += n

add_global(5)
add_global(3)
print(total)  # 8

# ✅ Using return values
def add_pure(total, n):
    return total + n

total = 0
total = add_pure(total, 5)
total = add_pure(total, 3)
print(total)  # 8

Using a Class

# ✅ Better for complex state
class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1
        return self.count

c = Counter()
print(c.increment())  # 1
print(c.increment())  # 2

Using a Mutable Container (Workaround)

# Works without nonlocal — mutating, not reassigning
def outer():
    state = {"count": 0}

    def inner():
        state["count"] += 1   # mutating is allowed without nonlocal

    inner()
    inner()
    print(state["count"])  # 2

outer()

global vs nonlocal — Comparison

Feature global nonlocal
Targets Module-level scope Nearest enclosing function scope
Works in Any function Only nested functions
Common use Module-wide state Closures and nested function state
Best avoided? Usually yes More acceptable in closures

Summary

  • global lets you modify module-level variables inside functions
  • nonlocal lets you modify enclosing function variables in nested functions
  • Both should be used sparingly — prefer passing arguments and returning values
  • For complex state, use a class instead of global or nonlocal
  • If you find yourself using global often, rethink your design

Next up: Closures →

Updated: