Python LEGB Rule: Variable Scope Resolution Guide
In Python, the LEGB rule defines the precise order in which the
interpreter resolves variable names. Whenever a variable is referenced,
Python searches through four hierarchical namespaces: Local, Enclosing,
Global, and Built-in. If the variable is not found in any of these four
levels, Python raises a NameError. Understanding this
lookup sequence is essential for avoiding unintended variable shadowing,
debugging naming conflicts, and managing state across nested functions
and modules.
The Four Scopes of LEGB
Python inspects scopes sequentially from the narrowest to the broadest:
Local --> Enclosing --> Global --> Built-in
1. Local (L)
The Local scope consists of variables defined directly inside the current function or lambda expression. These variables are initialized when the function is called and destroyed when the function returns.
def calculate_tax():
rate = 0.05 # Local scope
print(rate)
calculate_tax()
# print(rate) # Raises NameError: 'rate' is not defined outside the function2. Enclosing (E)
The Enclosing (or nonlocal) scope applies to nested functions. It refers to the namespace of any outer or enclosing functions. Python checks the immediate outer function first, moving outwards through any additional enclosing layers before proceeding to the global level.
def outer_function():
message = "Enclosing Scope" # Enclosing variable
def inner_function():
print(message) # Accesses 'message' from enclosing scope
inner_function()
outer_function()3. Global (G)
The Global scope encompasses variables defined at the top level of a
module or script, outside of any function or class block. It also
includes variables explicitly declared inside a function using the
global keyword.
app_name = "Inventory Manager" # Global scope
def show_app():
print(app_name) # Resolves to the global variable
show_app()4. Built-in (B)
The Built-in scope is the widest and final scope Python evaluates. It
contains all predefined functions, exceptions, and keywords provided by
the standard runtime (such as print(), len(),
range(), and ValueError). This scope is loaded
automatically via the builtins module.
# 'len' is resolved from the Built-in scope
numbers = [1, 2, 3]
print(len(numbers))How Resolution Works
When Python encounters an identifier, it terminates the search at the first matching name it finds along the LEGB chain:
- Check Local: Is the variable defined within the current function? If yes, use it.
- Check Enclosing: Is the variable defined in an outer function? If yes, use it.
- Check Global: Is the variable defined at the module level? If yes, use it.
- Check Built-in: Does the variable exist as a built-in Python function or constant? If yes, use it.
- Fail: If all scopes are exhausted without a match,
raise a
NameError.
Resolution Order Demonstration
x = "Global"
def outer():
x = "Enclosing"
def inner():
x = "Local"
print(x) # Prints "Local"
inner()
outer()If you comment out x = "Local", the output becomes
"Enclosing". If you also comment out
x = "Enclosing", the output becomes
"Global".
Modifying Scopes:
global and nonlocal
By default, assigning to a variable inside a function creates or updates a local variable; it does not overwrite variables in outer scopes. To alter that behavior, Python provides two keywords:
global: Directs Python to bind the variable directly to the top-level module scope, allowing write operations to global state.nonlocal: Directs Python to rebind a variable in the nearest enclosing function scope, preventing the creation of a local duplicate.
counter = 0
def update_global():
global counter
counter += 1
def outer_counter():
count = 0
def inner_increment():
nonlocal count
count += 1
return count
return inner_incrementBy adhering strictly to the LEGB sequence, Python keeps state management predictable and isolates nested code blocks while still permitting intentional access to broader execution contexts.