Python
You Thought You Knew Python Scope? Let's Clear Up the Confusion Once and For All
Understand Python's LEGB scope rule, why UnboundLocalError happens, and how to correctly use global and nonlocal keywords to control variable visibility and assignment.
June 2026 · 5 min read · 1 views · 0 hearts
Advertisement
You Thought You Knew Python Scope? Let's Clear Up the Confusion Once and For All
If you've ever been bitten by a UnboundLocalError or wondered why a variable inside a function suddenly "forgot" its global value, you're not alone. Python's scope rules are elegant once you understand them, but they can feel like a trap until you do.
Let's walk through how Python decides which variable you're talking about—and why sometimes it's not the one you think.
The LEGB Rule: Your Mental Map
Python looks up variable names in a strict order: Local, Enclosing, Global, Built-in. Think of it as a set of nested boxes.
- Local: inside the current function
- Enclosing: any outer function that wraps the current one (for nested functions)
- Global: the top level of the module (your script file)
- Built-in: Python's predefined names like
print,len,range
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local
inner()
print(x) # enclosing
outer()
print(x) # global
Each x is a separate variable. Python finds the closest match in the LEGB chain and stops.
The Trap: Assignment Makes a Variable Local (Unless You Say Otherwise)
Here's where most people get burned:
count = 0
def increment():
count += 1 # UnboundLocalError!
increment()
Why does this crash? Because count += 1 is an assignment. Python sees an assignment inside the function and treats count as a local variable—before it ever sees the += operation. The local count hasn't been given a value yet, so it raises UnboundLocalError.
The fix is the global keyword:
count = 0
def increment():
global count
count += 1
increment()
print(count) # 1
Nested Functions Need nonlocal, Not global
When you're inside a nested function and want to modify a variable from an outer (but not global) scope, global won't help. You need nonlocal:
def outer():
x = "outer value"
def inner():
nonlocal x
x = "changed by inner"
inner()
print(x) # "changed by inner"
outer()
Without nonlocal, the assignment x = "changed by inner" would create a new local variable inside inner, leaving the outer x untouched.
When You Can Read But Not Write—Unless It's Mutable
Here's a subtle twist: you can read a global variable inside a function without saying global:
greeting = "Hello"
def say_hello():
print(greeting) # Works fine, reads global
say_hello() # Hello
But if you assign to it, you need global. However, there's a workaround for mutable objects like lists or dicts:
items = []
def add_item(item):
items.append(item) # No error! Modifying, not reassigning
add_item("apple")
print(items) # ['apple']
Why does this work? Because items.append doesn't rebind the name items—it mutates the list object that items points to. The variable items is still the global one. You're only modifying, not reassigning.
The Built-in Scope: Don't Shadow It
Python's built-in names are always available, but you can accidentally override them:
def my_print():
print = "I'm a string now"
print("Hello") # TypeError!
my_print()
Once you assign to print locally, the built-in print function is hidden for the rest of that scope. A good practice: never use built-in names for your own variables.
Quick Cheat Sheet
| Situation | What to use |
|---|---|
| Read a global variable | Nothing needed |
| Assign to a global variable | global variable_name |
| Assign to a variable in an enclosing function | nonlocal variable_name |
| Modify a mutable global (list, dict, set) | Nothing needed (but careful) |
| Avoid name clashes with built-ins | Choose different variable names |
The One-Liner That Explains It All
Python's scoping is lexical (determined at compile time), and assignment binds a name to the local scope by default.
Run with that understanding, and you'll stop chasing ghost bugs. The LEGB rule and the global/nonlocal keywords are your tools—not your enemies. Use them intentionally, and your code will be cleaner, clearer, and much less surprising.
Advertisement
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.