W3docs

Python Scope

Learn Python scope and the LEGB rule: local, enclosing, global, and built-in scopes with clear examples, gotchas, and the global/nonlocal keywords.

Understanding Python Scope

Scope determines where a variable name is visible and how long it lives. Every name you create — a variable, a function, a class — belongs to exactly one scope. When Python encounters a name, it searches scopes in a strict order until it finds the name or raises a NameError.

This page covers:

  • The four scope levels and the LEGB lookup rule
  • global and nonlocal keywords
  • Common pitfalls: shadowing, UnboundLocalError, and name leakage
  • How to inspect scopes at runtime

The LEGB Rule

Python resolves names in four layers, searched from innermost to outermost:

LevelStands forWhat it contains
LLocalNames defined inside the current function
EEnclosingNames in any enclosing (outer) function, for nested functions
GGlobalNames defined at the top level of the current module
BBuilt-inNames pre-loaded by Python — print, len, range, type, etc.

Python stops at the first match. If no match is found in any layer, a NameError is raised.

Local Scope

A name is local when it is assigned inside a function. It is created when the function is called and destroyed when the function returns. Local names are invisible outside the function.

def greet():
    message = "Hello, World!"  # local variable
    print(message)             # accessible here

greet()
# Output: Hello, World!

print(message)  # NameError: name 'message' is not defined

Why local scope matters

Local scope lets you use simple, descriptive names like total, result, or i inside every function without any of them clashing with each other or with module-level code.

def calculate_area(radius):
    pi = 3.14159        # local to this function
    area = pi * radius ** 2
    return area

def calculate_volume(radius, height):
    pi = 3.14159        # completely separate local 'pi'
    volume = pi * radius ** 2 * height
    return volume

print(calculate_area(5))       # 78.53975
print(calculate_volume(5, 10)) # 785.3975

Both functions use pi independently. There is no conflict.

Enclosing Scope

When a function is defined inside another function, the inner function can read names from the outer (enclosing) function. This is the E in LEGB.

def outer():
    color = "blue"      # enclosing variable

    def inner():
        print(color)    # reads from enclosing scope

    inner()

outer()
# Output: blue

The inner function can read color without any special keyword because Python walks up to the enclosing scope automatically.

Modifying an enclosing variable with nonlocal

Reading an enclosing variable is automatic, but assigning to it requires the nonlocal keyword. Without it, Python treats the assignment as creating a new local variable, not modifying the enclosing one.

def make_counter():
    count = 0           # enclosing variable

    def increment():
        nonlocal count  # declare intent to modify the enclosing 'count'
        count += 1
        return count

    return increment

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

Each call to counter() modifies the same count variable. This pattern is the basis of closures — see Python Closures for a deeper look.

Global Scope

Names defined at the top level of a module (outside any function) are global. They are accessible from anywhere in the same module, including inside functions.

language = "Python"     # global variable

def display_language():
    print(language)     # reads global without any keyword

display_language()
# Output: Python

Modifying a global variable with global

You can read a global variable from inside a function without any keyword. But if you want to assign to it, you must declare it with global first:

total = 0               # global variable

def add_to_total(n):
    global total        # declare intent to modify the global
    total += n

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

Without global total, the line total += n would raise an UnboundLocalError because Python would treat total as a local variable that has not been assigned yet.

For more on global variables see Python Global Variables.

When to avoid global

Using global extensively makes code harder to test and reason about. Prefer returning values from functions and passing data as arguments. Reserve global for genuine module-level state — configuration flags, counters, or caches — and keep it rare.

Built-in Scope

The built-in scope contains names that Python provides automatically. They are available in every module without any import.

python— editable, runs on the server

Common built-in names include print, len, range, type, int, str, list, dict, set, tuple, open, input, max, min, sum, sorted, enumerate, zip, and map.

Do not shadow built-in names

Because the built-in scope is searched last, any local or global name with the same spelling will shadow it. This is a common mistake:

# Bad: shadows the built-in 'list'
list = [1, 2, 3]
print(type(list))   # <class 'list'> — still works here

new = list([4, 5])  # TypeError: 'list' object is not callable

Delete the shadowing name to restore the built-in:

del list            # remove the local/global shadowing name
new = list([4, 5])  # works again: [4, 5]

The built-in module is available as builtins if you ever need to access it explicitly:

import builtins
print(builtins.len([1, 2, 3]))  # 3

Common Gotchas

UnboundLocalError

The most frequent scope mistake in Python is reading a name before assigning it inside a function when there is also an assignment to that name anywhere in the same function:

x = 10

def bad_func():
    print(x)   # UnboundLocalError! Python sees x = 20 below
    x = 20     # this makes 'x' local for the whole function

bad_func()

Python decides at compile time that x is local (because it is assigned in the function). The attempt to read it before the assignment raises UnboundLocalError: local variable 'x' referenced before assignment.

Fix it by either declaring global x or by not assigning to x inside the function.

Variable shadowing

A local variable that shares a name with a global silently shadows the global. This is usually intentional, but it can cause subtle bugs when you expect to be reading the global:

x = 10          # global

def my_func():
    x = 20      # local — shadows the global
    print(x)    # 20, not 10

my_func()
print(x)        # 10 — global unchanged

Inspecting Scope at Runtime

Python exposes the current scope contents through two built-in functions.

locals() returns a dictionary of the current local scope (or the module-level namespace if called at the top level):

def show_locals():
    a = 1
    b = "hello"
    print(locals())   # {'a': 1, 'b': 'hello'}

show_locals()

globals() returns the global (module-level) namespace dictionary:

language = "Python"
print("language" in globals())  # True

These are useful for debugging and introspection but should not be used to manipulate variables dynamically in production code.

Scope and Functions

Functions in Python are objects and are themselves names that live in some scope. A function defined at the top of a module is global; a function defined inside another function is local to the enclosing function.

def outer():
    def helper():       # helper is local to outer
        return 42
    return helper()

print(outer())          # 42
# helper()              # NameError: helper is not defined here

See Python Functions for how functions are defined, and Python Lambda for anonymous functions that also obey the LEGB rule.

Quick Reference

KeywordEffect
(none)Read from the nearest enclosing scope by LEGB lookup
global xTell the current function that x refers to the module-level name
nonlocal xTell the current function that x refers to the name in the nearest enclosing function

Practice

Practice
In Python, what are the four types of scopes in the order that they are checked?
In Python, what are the four types of scopes in the order that they are checked?
Was this page helpful?