How Python’s VM Resolves Variable Lookups: LEGB, locals(), globals() and nonlocal
The article explains Python's variable lookup mechanism, detailing the LEGB rule, static storage of local variables, dynamic global namespace updates, common UnboundLocalError scenarios, bytecode inspection, and how the global and nonlocal keywords modify scope resolution.
Python resolves variable names using the LEGB rule (Local, Enclosing, Global, Built‑in). For functions, local variables are stored statically; the compiler determines their names from co_varnames, so any assignment makes the name visible throughout the function body.
Global variables are created by adding a key‑value pair to the global dictionary, which is why globals()["x"] = 1 defines a global variable at runtime.
When a function assigns to a name, Python treats that name as local for the entire function. Attempting to read it before the assignment raises UnboundLocalError. Example:
x = 1
def foo():
print(x) # UnboundLocalError
x = 2
foo()Disassembling the function shows LOAD_FAST_CHECK for the local name, confirming static lookup.
import dis
def bar():
print(x)
x = 2
dis.dis(bar)
# ... LOAD_FAST_CHECK ...To access the global variable instead, use the global keyword:
x = 1
def bar():
global x
print(x) # prints 1
x = 2
bar()
print(x) # 2For nested functions, nonlocal binds a name to the nearest enclosing scope, allowing the inner function to read and modify the outer variable:
def outer():
x = 1
def inner():
nonlocal x
print(x) # 1
x = 2
return inner
f = outer()
f() # prints 1, x becomes 2 in outerAttribute lookup works similarly but is confined to the object's attribute dictionary. Accessing a.xxx searches a.__dict__ (or the class’s __dict__) without involving LEGB.
import numpy as np
print(np.array([1,2,3]))
print(np.__dict__["array"]([4,5,6]))Modules have their own namespaces; importing a name from another module does not cross module boundaries. Attempting to import an undefined name raises NameError.
# girl.py
print(name)
# main.py
name = "Alice"
from girl import name # NameErrorBuilt‑in objects reside in the __builtins__ module, which lives in the global namespace. Because globals() and __builtins__ reference each other, repeated nesting of these lookups still resolves correctly.
print(globals()["__builtins__"].globals()["__builtins__"].list("abc"))
# ['a', 'b', 'c']In summary, Python’s variable lookup is a static‑first strategy for locals, a dynamic dictionary for globals, and respects the enclosing scope via nonlocal. Attribute access follows a straightforward dictionary lookup within the object's own namespace.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Satori Komeiji's Programming Classroom
Python and Rust developer; I write about any topics you're interested in. Follow me! (#^.^#)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
