🔍 Scope Resolution¶
🚀 Open Notebook¶
📺 Video Tutorial¶
What is Python scope resolution? 🔬 (8:05)
What You’ll Learn¶
In this chapter, you’ll master Python’s scope resolution - understanding where variables are accessible and the order Python searches for them. You’ll learn the LEGB rule (Local, Enclosing, Global, Built-in), understand variable shadowing, use the global and nonlocal keywords, and write code that properly manages variable scope across functions and modules.
Learning Objectives¶
Understand the four levels of variable scope in Python
Apply the LEGB rule to predict variable resolution
Distinguish between local, enclosing, global, and built-in scopes
Use
globalkeyword to modify global variables from functionsUse
nonlocalkeyword to modify enclosing function variablesAvoid common scope-related bugs and write cleaner code
Concept Explanation¶
What is Variable Scope?¶
Scope determines where a variable is visible and accessible in your code. Not all variables are available everywhere - scope controls access.
x = 10 # Global scope - accessible everywhere
def my_function():
y = 20 # Local scope - only accessible inside function
print(x) # Can access global x
print(y) # Can access local y
my_function()
print(x) # Works - x is global
print(y) # Error! y doesn't exist here
The LEGB Rule¶
Python searches for variables in this order: Local → Enclosing → Global → Built-in
B (Built-in) ← Python's built-in names (print, len, etc.)
↑
G (Global) ← Variables at module level
↑
E (Enclosing) ← Variables in enclosing functions
↑
L (Local) ← Variables inside current function
Search Process:
Look in Local scope first
If not found, look in Enclosing scope
If not found, look in Global scope
If not found, look in Built-in scope
If still not found →
NameError
1. Local Scope (L)¶
Variables defined inside a function are local to that function:
def my_function():
x = 10 # Local variable
print(x) # 10
my_function()
print(x) # NameError: x is not defined
Key Points:
Local variables only exist during function execution
Each function call creates a new local scope
Local variables are destroyed when function returns
2. Enclosing Scope (E)¶
For nested functions, the outer function’s scope is the enclosing scope:
def outer():
x = 10 # Enclosing scope for inner()
def inner():
print(x) # Accesses x from enclosing scope
inner() # Prints 10
outer()
3. Global Scope (G)¶
Variables defined at the module level (outside all functions) are global:
x = 100 # Global variable
def func1():
print(x) # Accesses global x
def func2():
print(x) # Also accesses same global x
func1() # 100
func2() # 100
4. Built-in Scope (B)¶
Python’s built-in names (functions like print(), len(), type()):
# Built-in functions available everywhere
print("Hello") # 'print' is in built-in scope
result = len([1, 2, 3]) # 'len' is in built-in scope
Variable Shadowing¶
Inner scopes can “shadow” (hide) outer scope variables:
x = 100 # Global
def my_function():
x = 10 # Local - shadows global x
print(x) # 10 (local x)
my_function() # Prints 10
print(x) # Prints 100 (global x unchanged)
The global Keyword¶
Use global to modify global variables from inside a function:
Without global (Creates local variable):
x = 100
def modify():
x = 200 # Creates NEW local variable
print(f"Inside: {x}") # 200
modify()
print(f"Outside: {x}") # 100 (global unchanged)
With global (Modifies global variable):
x = 100
def modify():
global x # Declare we're using global x
x = 200 # Modifies the global x
print(f"Inside: {x}") # 200
modify()
print(f"Outside: {x}") # 200 (global changed)
The nonlocal Keyword¶
Use nonlocal to modify variables in enclosing function scope:
def outer():
x = 10
def inner():
nonlocal x # Refers to outer's x
x = 20 # Modifies outer's x
print(f"Inner: {x}") # 20
inner()
print(f"Outer: {x}") # 20 (modified by inner)
outer()
Scope Best Practices¶
1. Minimize Global Variables¶
# Bad - global variables
count = 0
total = 0
def add_value(x):
global count, total
count += 1
total += x
# Good - pass and return values
def add_value(count, total, x):
count += 1
total += x
return count, total
count, total = add_value(count, total, 5)
2. Use Function Parameters¶
# Bad - relies on global
x = 10
def double():
return x * 2
# Good - explicit parameter
def double(x):
return x * 2
result = double(10)
3. Return Values Instead of Modifying Globals¶
# Bad
result = 0
def calculate(x):
global result
result = x * 2
# Good
def calculate(x):
return x * 2
result = calculate(10)
Understanding Scope with Examples¶
Example: Independent Local Scopes¶
def func1():
x = 10 # Local to func1
print(f"func1: {x}")
def func2():
x = 20 # Different local variable (separate scope)
print(f"func2: {x}")
func1() # func1: 10
func2() # func2: 20
# Each function has its own x
Example: LEGB in Action¶
x = "global" # Global scope
def outer():
x = "enclosing" # Enclosing scope
def inner():
x = "local" # Local scope
print(x) # Prints "local" (found in L)
inner()
print(x) # Prints "enclosing" (local to outer)
outer()
print(x) # Prints "global"
Examples¶
Example 1: Basic Local vs Global¶
x = 100 # Global variable
def func1():
"""Function with its own local x."""
x = 10 # Local variable
print(f"Inside func1, x = {x}")
def func2():
"""Function with different local x."""
x = 20 # Different local variable
print(f"Inside func2, x = {x}")
func1() # Inside func1, x = 10
func2() # Inside func2, x = 20
print(f"Global x = {x}") # Global x = 100
Example 2: Accessing Global Variables¶
total = 0 # Global variable
def add_to_total(value):
"""This only reads global total, doesn't modify it."""
print(f"Current total: {total}")
print(f"Adding {value}")
# Note: We're reading total, not modifying it
add_to_total(10) # Current total: 0, Adding 10
print(f"Total is still: {total}") # 0
Example 3: Modifying Global with global¶
counter = 0 # Global counter
def increment():
"""Increment the global counter."""
global counter # Declare we're using global variable
counter += 1
print(f"Counter: {counter}")
increment() # Counter: 1
increment() # Counter: 2
increment() # Counter: 3
print(f"Final counter: {counter}") # Final counter: 3
Example 4: Enclosing Scope with Nested Functions¶
def outer(x):
"""Outer function with enclosing scope."""
def inner():
"""Inner function accessing enclosing scope."""
print(f"Inner can see x from outer: {x}")
print(f"Outer x: {x}")
inner()
outer(42)
# Outer x: 42
# Inner can see x from outer: 42
Example 5: Using nonlocal for Closures¶
def make_counter():
"""Create a counter function with persistent state."""
count = 0 # Enclosing scope variable
def counter():
nonlocal count # Modify enclosing scope variable
count += 1
return count
return counter
# Create two independent counters
counter1 = make_counter()
counter2 = make_counter()
print(counter1()) # 1
print(counter1()) # 2
print(counter1()) # 3
print(counter2()) # 1 (independent counter)
print(counter2()) # 2
Example 6: LEGB Resolution Order¶
x = "global x" # Global
def outer():
x = "outer x" # Enclosing
def inner():
x = "inner x" # Local
print(f"1. Local: {x}") # Finds x in Local scope
def inner2():
# No local x, finds in enclosing
print(f"2. Enclosing: {x}") # Finds x in Enclosing scope
inner()
inner2()
def no_local():
# No local or enclosing x, finds in global
print(f"3. Global: {x}") # Finds x in Global scope
outer()
# 1. Local: inner x
# 2. Enclosing: outer x
no_local()
# 3. Global: global x
print(f"4. Global: {x}") # global x
Example 7: Shopping Cart with Scope¶
# Global variables (generally avoid, but for demonstration)
cart_items = []
cart_total = 0.0
def add_item(name, price):
"""Add item to shopping cart."""
global cart_items, cart_total
cart_items.append({"name": name, "price": price})
cart_total += price
print(f"Added {name} (${price:.2f})")
print(f"Total: ${cart_total:.2f}")
def clear_cart():
"""Clear the shopping cart."""
global cart_items, cart_total
cart_items = []
cart_total = 0.0
print("Cart cleared!")
def show_cart():
"""Display cart contents."""
print("\n=== Shopping Cart ===")
for item in cart_items:
print(f" {item['name']}: ${item['price']:.2f}")
print(f"Total: ${cart_total:.2f}")
print("=" * 21)
# Use the cart functions
add_item("Apple", 1.50)
add_item("Banana", 0.75)
add_item("Orange", 2.00)
show_cart()
clear_cart()
show_cart()
Practice Exercises¶
Beginner Level¶
Scope Tester: Create functions that demonstrate local vs global scope with print statements.
Counter Function: Build a function that uses
globalto maintain a counter across calls.Nested Access: Write nested functions where inner function accesses outer function’s variable.
Variable Shadow: Demonstrate variable shadowing with same-named variables in different scopes.
Built-in Override: Show what happens when you create a variable with a built-in name.
Intermediate Level¶
Closure Factory: Create a function that returns another function with access to enclosing scope.
State Manager: Build functions using
nonlocalto maintain state in nested functions.Scope Debugger: Write a function that prints all variables in different scopes.
Bank Account: Create account functions using scope to protect balance variable.
Configuration System: Use global variables for app configuration with getter/setter functions.
Advanced Level¶
Decorator with State: Create a decorator that uses closure to maintain state across decorated function calls.
Namespace Manager: Build a system that manages different namespaces using scope.
Singleton Pattern: Implement singleton pattern using closure and scope.
Function Factory: Create a factory function that returns customized functions using scope.
Scope Chain Visualizer: Build a tool that visualizes the scope chain for complex nested functions.
Common Mistakes to Avoid¶
Mistake 1: Modifying Global Without global¶
Wrong:
count = 0
def increment():
count = count + 1 # UnboundLocalError!
# Python sees assignment, treats count as local
# But tries to read it before assignment
increment()
Correct:
count = 0
def increment():
global count # Declare using global count
count = count + 1
increment()
print(count) # 1
Why: Assignment creates local variable. Use global to modify global.
Mistake 2: Assuming Local Changes Affect Global¶
Wrong:
total = 100
def modify_total():
total = 200 # Creates local variable, doesn't affect global
modify_total()
print(total) # Still 100!
Correct:
# Option 1: Use global
total = 100
def modify_total():
global total
total = 200
modify_total()
print(total) # 200
# Option 2: Return value (better)
def modify_total(value):
return value * 2
total = modify_total(total)
print(total) # 200
Why: Local assignment doesn’t affect global. Use global or return values.
Mistake 3: Wrong Use of nonlocal¶
Wrong:
x = 10
def func():
nonlocal x # SyntaxError: no binding for nonlocal 'x'
x = 20
# nonlocal only works in nested functions!
Correct:
def outer():
x = 10
def inner():
nonlocal x # Now it works - x is in enclosing scope
x = 20
inner()
print(x) # 20
outer()
Why: nonlocal only works for enclosing function scopes, not global.
Mistake 4: Overusing Global Variables¶
Wrong:
# Bad - too many globals
user_name = ""
user_age = 0
user_email = ""
def set_user_info(name, age, email):
global user_name, user_age, user_email
user_name = name
user_age = age
user_email = email
Correct:
# Good - use parameters and returns
def create_user(name, age, email):
return {
"name": name,
"age": age,
"email": email
}
user = create_user("Alice", 25, "alice@example.com")
Why: Global variables make code harder to test and maintain. Prefer parameters and return values.
Real-World Applications¶
1. Configuration Management¶
DEBUG = False # Global config
DATABASE_URL = "localhost:5432"
def setup_app():
global DEBUG
if environment == "development":
DEBUG = True
2. Closures for Decorators¶
def cache_result(func):
cached = {} # Enclosing scope
def wrapper(*args):
if args not in cached:
cached[args] = func(*args)
return cached[args]
return wrapper
3. State Machines¶
def create_state_machine():
state = "idle" # Enclosing scope
def transition(new_state):
nonlocal state
state = new_state
def get_state():
return state
return transition, get_state
4. Singleton Pattern¶
_instance = None # Global
def get_instance():
global _instance
if _instance is None:
_instance = DatabaseConnection()
return _instance
Challenge Projects¶
1. Function Call Tracker¶
Build a system that tracks function calls using scope.
Requirements:
Track number of calls per function
Track total execution time
Use closures and decorators
Report statistics
Reset functionality
2. Namespace Manager¶
Create a namespace management system.
Requirements:
Multiple isolated namespaces
Variable get/set operations
Scope chain visualization
Import/export variables
Namespace inheritance
3. State Machine Framework¶
Build a state machine using scope management.
Requirements:
State transitions using nonlocal
State history tracking
Rollback functionality
Event handlers
State validation
4. Advanced Counter System¶
Create various types of counters using scope.
Requirements:
Simple counter with increment/decrement
Rate-limited counter
Bounded counter (min/max)
Multi-counter manager
Thread-safe version (bonus)
5. Configuration System¶
Build a hierarchical configuration system.
Requirements:
Global defaults
Environment-specific configs
Override mechanism
Config validation
Save/load from files
Type checking
🎓 Key Takeaways from Video¶
Variables store data values that can be reused
Define functions using the def keyword
Follow along with the video for hands-on practice
💡 These points cover the main concepts from the video tutorial to help reinforce your learning.