Variable Scope
Understanding scope is essential for writing bug-free Python code. Scope determines where variables can be accessed and modified. Python follows the LEGB rule - Local, Enclosing, Global, Built-in - to look up variable names. Master scope and you'll avoid one of the most common sources of confusion in programming!
Local Scope
Variables created inside a function are local to that function. They're created when the function is called and destroyed when it returns. Local variables cannot be accessed from outside the function, and each function call gets fresh local variables.
Click Run to execute your code
Global Scope and the global Keyword
Variables defined at the module level (outside any function) are global. Functions
can read global variables freely, but to modify them, you must use the global
keyword. However, overusing global variables is generally considered bad practice.
Click Run to execute your code
PI = 3.14159) or configuration that genuinely needs to be module-wide.
The LEGB Rule
When Python encounters a variable name, it searches for it in a specific order: Local, Enclosing, Global, Built-in. This is the LEGB rule. Understanding it helps you predict which variable Python will find when names overlap.
Click Run to execute your code
len,
print). Python stops at the first match it finds.
The nonlocal Keyword
When you have nested functions and need to modify a variable from an enclosing (but not
global) scope, use nonlocal. This is especially useful for creating closures -
functions that "remember" state from their enclosing scope.
Click Run to execute your code
Common Mistakes
1. UnboundLocalError when modifying globals
count = 0
def increment():
count += 1 # UnboundLocalError!
# Python sees assignment, creates local 'count'
# But tries to read count before it's assigned
# Fix: use global keyword
def increment():
global count
count += 1
2. Shadowing built-in names
# Bad - shadows built-in list()
list = [1, 2, 3]
# Later...
new_list = list("hello") # TypeError: 'list' object is not callable
# Fix: use different variable names
items = [1, 2, 3]
new_list = list("hello") # Works: ['h', 'e', 'l', 'l', 'o']
3. Thinking assignment creates reference to global
data = [1, 2, 3]
def modify():
data = [4, 5, 6] # Creates LOCAL data!
# Global data is unchanged
modify()
print(data) # [1, 2, 3] - surprised?
# If you want to modify the global:
def modify():
global data
data = [4, 5, 6]
4. Using global when nonlocal is needed
def outer():
x = 10
def inner():
global x # Wrong! Looks for module-level x
x += 1
inner()
print(x) # Still 10 - inner modified a different x
# Fix: use nonlocal for enclosing scope
def outer():
x = 10
def inner():
nonlocal x # Correct!
x += 1
inner()
print(x) # 11
5. Mutable globals can be modified without 'global'
# Surprising: this works without 'global'
items = []
def add_item(item):
items.append(item) # Modifying, not reassigning!
add_item("apple")
print(items) # ['apple']
# But this needs 'global':
def reset_items():
global items
items = [] # Reassignment needs global
Exercise: Counter Factory
Task: Create a function that returns a counter function using closures.
Requirements:
- Create
make_counter(start=0) - Returns a function that increments and returns the count
- Use
nonlocalto maintain state
Click Run to execute your code
Show Solution
def make_counter(start=0):
"""Create a counter function starting at 'start'."""
count = start
def counter():
nonlocal count
count += 1
return count
return counter
# Test it
counter1 = make_counter()
counter2 = make_counter(100)
print(f"Counter 1: {counter1()}, {counter1()}, {counter1()}")
print(f"Counter 2: {counter2()}, {counter2()}")
Summary
- Local: Variables inside functions - created on call, destroyed on return
- Global: Variables at module level - use
globalkeyword to modify - LEGB: Lookup order: Local → Enclosing → Global → Built-in
- nonlocal: Modify enclosing (not global) scope in nested functions
- Shadowing: Local variables can shadow global/built-in names
- Best practice: Avoid global state; prefer passing values and returning results
- Closures: Functions that capture and remember enclosing scope
What's Next?
Now that you understand scope, let's learn about Lambda Functions -
small, anonymous functions perfect for short operations. Lambdas are widely used with
functions like map(), filter(), and sorted()!
Enjoying these tutorials?