🚀 Supercharge your YouTube channel's growth with AI.
Try YTGrowAI FreePython Namespace and Variable Scope Resolution (LEGB)

When a function uses a name, Python searches the namespaces visible to that code block to find its binding. Scope is the region where a name can be resolved, and LEGB names the order Python searches those namespaces.
I’ll trace name lookup through LEGB, then show why reading a local name before assigning to it raises an error.
TL;DR: Python variable scope follows LEGB
Python looks for a name in the current Local scope, then enclosing function scopes, the module Global scope, and the Built-in namespace. A name assigned inside a function is local to that function unless the function declares it global or nonlocal.
- Namespaces map names to objects. Scope determines where a name can be used.
- Assignments in if and loop blocks do not create a separate function scope.
- Use global for a module-level binding and nonlocal for a binding in an enclosing function.
What is Python scope and namespace?
A namespace is a mapping from names to objects, while scope is the region where a name can be resolved. When Python evaluates a name, it searches the namespaces visible to the current code block.
Functions introduce local scopes, and nested functions can see names from enclosing functions. A module has its own global namespace, and Python supplies a built-in namespace for names such as len. Indented if and for blocks do not create a new function scope.
Trace a name through Python’s LEGB scopes
This example gives x a different binding at each nested level. The call to len also resolves through the built-in namespace.
Step 1: Compare local, enclosing, and global names
Each print reads the nearest binding visible from that function. The same identifier can refer to different objects at different levels.
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print("local:", x)
print("builtin:", len("abc"))
inner()
print("enclosing:", x)
outer()
print("global:", x)
The output shows each x at its own level, while len resolves to the built-in function.
local: local
builtin: 3
enclosing: enclosing
global: global
Step 2: Rebind an enclosing name with nonlocal
An assignment inside inner normally creates a local name. The nonlocal declaration instead points the assignment at the nearest enclosing function binding.
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
next_count = make_counter()
print("counter:", next_count(), next_count())
The returned function retains access to count, and the two calls print increasing values.
counter: 1 2
When scope lookup behaves differently than expected
The lookup order is only part of the rule. Python classifies a function name as local when that function binds it anywhere in its body, so reading it before the assignment raises UnboundLocalError rather than falling through to the global binding.
This is why a variable assigned in an if statement or loop can still be available later in the same function. Those blocks do not create their own function scope, although the name may not have been assigned if the branch or loop body never ran.
Use global when a function must rebind a module-level name. Use nonlocal when it must rebind a name in an enclosing function. Without either declaration, assignment binds a new local name.
Check the local-binding error
I ran this function with the global x from the earlier example. Because the assignment makes x local throughout the function, the first print cannot read the global value.
def read_then_assign():
print(x)
x = "local"
read_then_assign()
The run raised UnboundLocalError at print(x). A name that is not bound in any visible scope raises NameError instead. UnboundLocalError is a subclass of NameError.
Traceback (most recent call last):
File "<string>", line 5, in <module>
File "<string>", line 2, in read_then_assign
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
Common errors with Python variable scope
A local assignment can prevent a function from reading a same-named global before that assignment runs. Python decides the name is local from the assignment anywhere in the function body, so the earlier read raises UnboundLocalError.
To fix it, move the assignment before the read when the value is local, or declare global or nonlocal when the function must rebind an outer name. A name with no binding in any visible scope raises NameError instead.
- Read before local assignment: move the assignment or declare the intended outer binding.
- Missing name: check each visible scope, including built-ins.
Conclusion: Keep name lookup predictable
When a name surprises you, first check where the function binds it, then trace outward through enclosing functions, the module, and built-ins. That separates a missing name from a local name that has not received a value yet.
For the exact rules, see Python’s execution model documentation and its section on global and nonlocal statements.
Do Python if and for blocks create a new scope?
No. They do not create a separate function scope. A name assigned in the block belongs to the surrounding function or module scope, subject to whether that assignment runs.
Why does Python raise UnboundLocalError?
A function binds a name locally if it assigns to that name anywhere in the function, unless declared global or nonlocal. Reading that local name before its value is assigned raises UnboundLocalError.
When should I use global or nonlocal?
Use global to rebind a module-level name from a function. Use nonlocal to rebind a name in the nearest enclosing function scope.


