MOD-21 · Sequence #21 · Advanced Python

Python Scope & LEGB Rule

Variable names are resolved in Python according to the LEGB rule: Local, Enclosing, Global, and Built-in. Understanding lexical scoping, the global and nonlocal keywords, and the root cause of UnboundLocalError is a hallmark of Python mastery.

Live Interactive Code Snippet (Python 3.12 CPython WASM) Open in Practice Mode →
x = 300
def myfunc():
    global x
    x = 200

myfunc()
print(x)  # 200

Core Principles & Mental Models

1. The LEGB Name Resolution Hierarchy

Four Concentric Scoping Environments

  • L (Local): Names assigned inside a function body.
  • E (Enclosing): Names in the local scope of any enclosing/outer functions (closures).
  • G (Global): Names assigned at the top-level of a module file or declared with the `global` keyword.
  • B (Built-in): Built-in Python names (print, len, range, ValueError, etc.).
  • Python searches in strict order: L -> E -> G -> B. The first scope where the name is found wins.
x = "Global x"
def outer():
    x = "Enclosing x"
    def inner():
        x = "Local x"
        print("Resolved:", x)
    inner()
outer()
💡 Key Takeaway: Python resolves names from innermost to outermost scope.

2. The `global` and `nonlocal` Keywords

Rebinding Variables in Outer Scopes

  • By default, assigning to a variable inside a function creates a new Local variable.
  • Use `global var_name` to rebind a module-level variable from inside a function.
  • Use `nonlocal var_name` to rebind an enclosing variable in nested functions/closures.
def make_counter():
    count = 0
    def step():
        nonlocal count  # Rebinds 'count' in enclosing scope
        count += 1
        return count
    return step

counter = make_counter()
print(counter(), counter(), counter())  # 1 2 3
💡 Key Takeaway: nonlocal is essential for stateful closures.

Interactive Walkthrough Examples

UnboundLocalError Root Cause Analysis (Intermediate)

Demonstrating why assigning to a variable marks it as local throughout the entire function.

x = 10
def problematic():
    # In Python, if a variable is assigned anywhere in the function,
    # CPython marks it as LOCAL at compile time for the ENTIRE function.
    try:
        # print(x) before assignment raises UnboundLocalError!
        pass
    except Exception as e:
        print(e)

def corrected():
    global x
    print("Global x read:", x)
    x = 20

corrected()
print("New global x:", x)

Explanation: Any assignment inside a function marks the identifier as local at compile time unless declared global.

Common Misconceptions & Exam Traps

Common Pitfall: Shadowing Built-in Names

Assigning to `list`, `str`, `dict`, or `id` shadows the Built-in namespace in LEGB order.

Buggy Code
list = [1, 2, 3]  # Shadows the built-in 'list' constructor!
# new_list = list("abc") -> TypeError: 'list' object is not callable!
Correct Solution
my_list = [1, 2, 3]  # Never use built-in names as identifiers

Exam Trap Note: Exams frequently ask why `list(range(5))` throws a TypeError after someone writes `list = [1, 2]`.

Conceptual Knowledge Self-Check Quiz

Q1: Which keyword allows a nested function to modify a variable in its enclosing outer function?

  1. global
  2. extern
  3. nonlocal
  4. super

`nonlocal` declares that an identifier refers to a variable in the nearest enclosing scope.

Algorithmic Practice Challenges

Idiomatic Best Practices

  • ✅ Avoid mutable global variables; pass state explicitly via parameters and return values.
  • ✅ Never name variables after built-in functions (e.g. `sum`, `max`, `min`, `list`, `type`).

All 26 Curriculum Modules