Variable Scope

Which name a piece of code can see, the four places Python looks, and why assigning inside a function does not change the outside.

Overview

The four places

LEGB is the order:

  • Local — names assigned in the current function
  • Enclosing — names in a function that wraps this one
  • Global — names at module level
  • Builtinlen, print, range and friends

The first match wins, which is why naming a variable list or sum shadows the builtin for the rest of that scope. It is legal and it will confuse you later.

scope.py

scope.py Python 3
Output

                    

legb.py

legb.py Python 3
Output

                    

Worth knowing

Python looks in four places, in order: Local, Enclosing, Global, Builtin.
Reading an outer name works. Assigning creates a local one instead.
One assignment anywhere makes the name local for the whole function - which is why reading it earlier raises UnboundLocalError.
global rebinds a module name; nonlocal rebinds the enclosing function's.

Variable Scope: A Practical Guide

When Python meets a name, it looks in four places in a fixed order — local, enclosing, global, builtin — and stops at the first one that has it. Almost every scope surprise comes from one rule about assignment.

Reading is easy, assigning is the trap

A function can read an outer name without ceremony:

x = "global"
def show():
    print(x)  # fine

But assigning to that name inside the function does not change the outer one. It creates a new local name that happens to be spelled the same, and it disappears when the function returns.

The rule that catches everyone

If a name is assigned anywhere in a function, it is local for the entire function — including lines that run before the assignment.

def broken():
    print(x)      # UnboundLocalError
    x = "too late"

That print looks like it should read the global, and it would have, if the line below it did not exist. Python decides local-or-not when it compiles the function, not while running it. The error message — "local variable referenced before assignment" — is precise once you know this, and baffling before.

global and nonlocal

To rebind rather than shadow, say so:

def bump():
    global count
    count += 1

global reaches module level. nonlocal reaches the nearest enclosing function, which is what makes closures able to keep state:

def counter():
    n = 0
    def step():
        nonlocal n
        n += 1

Both are worth knowing and neither is worth reaching for often. A function that rebinds globals is a function whose behaviour depends on when you call it, which is exactly the kind of thing that makes bugs hard to find. Returning a value is nearly always better.

Mutating is not assigning

One clarification that resolves a lot of confusion: items.append(1) is not an assignment. It mutates the object the name already points at, so it affects the outer list without needing global. items = [1] is an assignment, and creates a local. The distinction is the object versus the name.

Check yourself

0 of 3

Answer without scrolling back up.

  1. What order does Python search for a name?

  2. Why does reading `x` before `x = 1` inside a function raise?

  3. `items.append(1)` inside a function affects the outer list. Why no `global`?

Cheat sheet

Variable Scope

When Python meets a name, it looks in four places in a fixed order — local, enclosing, global, builtin — and stops at the first one that has it. Almost every scope surprise comes from one rule about assignment.

PYTHON · vizlearn.in/python/variable_scope.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.