🔍 Variable Scope
Variable scope determines where in your code a variable can be accessed and modified. Understanding scope helps you write functions that work predictably with data, avoid naming conflicts, and create maintainable code that handles variables appropriately.
# Different variable scopes in action
global_counter = 0 # Global scope
def increment_counter():
global global_counter # Access global variable
global_counter += 1
local_var = "I'm local" # Local scope
print(f"Counter: {global_counter}, Local: {local_var}")
def display_counter():
print(f"Current counter: {global_counter}") # Read global
# print(local_var) # Would cause error - not accessible
increment_counter()
increment_counter()
display_counter()
🎯 Local Scope
Local scope refers to variables created inside functions that are only accessible within that function. These variables exist only during function execution and are destroyed when the function completes.
def calculate_order_total(items, tax_rate):
"""Local variables exist only within this function"""
subtotal = sum(items) # Local variable
tax = subtotal * tax_rate # Local variable
total = subtotal + tax # Local variable
return total
def process_payment(amount):
"""Each function has its own local scope"""
processing_fee = 2.50 # Local to this function
final_amount = amount + processing_fee
print(f"Processing ${amount} + ${processing_fee} fee")
return final_amount
# Use functions with local variables
order_total = calculate_order_total([29.99, 15.50], 0.08)
payment_amount = process_payment(order_total)
print(f"Final payment: ${payment_amount:.2f}")
⚡ Global Scope
Global scope contains variables defined at the module level that can be accessed from anywhere in the code. Use the global
keyword to modify global variables inside functions.
# Global variables
app_name = "Shopping Cart"
total_orders = 0
def place_order(customer_name, items):
"""Function that modifies global variable"""
global total_orders
order_value = sum(items)
total_orders += 1 # Modify global counter
print(f"Order #{total_orders} for {customer_name}")
print(f"Order value: ${order_value}")
return total_orders
def get_stats():
"""Reading global variables doesn't need global keyword"""
return f"{app_name} has processed {total_orders} orders"
# Place some orders
order1 = place_order("Alice", [25.99, 12.50])
order2 = place_order("Bob", [45.00])
print(get_stats())
🚀 LEGB Rule (Scope Resolution)
Python resolves variable names using the LEGB rule: Local, Enclosing, Global, Built-in. This determines which variable is used when names conflict across different scopes.
name = "Global Name" # Global scope
def outer_function():
name = "Outer Name" # Enclosing scope
def inner_function():
name = "Inner Name" # Local scope
print(f"Inner sees: {name}") # Uses local
print(f"Built-in len: {len('test')}") # Built-in scope
def another_inner():
# No local 'name', uses enclosing scope
print(f"Another inner sees: {name}") # Uses enclosing
inner_function()
another_inner()
print(f"Outer sees: {name}") # Uses local (enclosing)
outer_function()
print(f"Global sees: {name}") # Uses global
🌟 Nonlocal Variables
The nonlocal
keyword allows inner functions to modify variables in their enclosing (outer) function scope, enabling advanced function patterns.
def create_counter(start_value=0):
"""Create a counter with persistent state"""
count = start_value # Enclosing scope variable
def increment():
nonlocal count # Modify enclosing variable
count += 1
return count
def get_current():
return count # Read enclosing variable (no nonlocal needed)
def reset():
nonlocal count
count = start_value
return count
return increment, get_current, reset
# Create and use counter
inc, get, reset = create_counter(10)
print(f"Current: {get()}") # 10
print(f"After increment: {inc()}") # 11
print(f"After increment: {inc()}") # 12
print(f"After reset: {reset()}") # 10
💡 Practical Applications
Configuration Functions
Global variables work well for application configuration that multiple functions need to access consistently.
# Global configuration
DEBUG_MODE = True
MAX_RETRY_ATTEMPTS = 3
def log_message(message, level="INFO"):
"""Log messages based on global configuration"""
if DEBUG_MODE or level == "ERROR":
print(f"[{level}] {message}")
def retry_operation(operation_name, attempts=0):
"""Use global retry limit"""
if attempts < MAX_RETRY_ATTEMPTS:
log_message(f"Attempting {operation_name} (try {attempts + 1})")
return attempts + 1
else:
log_message(f"Max retries reached for {operation_name}", "ERROR")
return -1
# Test configuration usage
log_message("Application starting")
attempt = retry_operation("database_connect", 0)
attempt = retry_operation("database_connect", attempt)
State Management
Functions can maintain private state using enclosing scope, creating clean interfaces without global variables.
def create_bank_account(initial_balance=0):
"""Create account with private balance"""
balance = initial_balance # Private to this closure
def deposit(amount):
nonlocal balance
if amount > 0:
balance += amount
return balance
return None
def withdraw(amount):
nonlocal balance
if 0 < amount <= balance:
balance -= amount
return balance
return None
def get_balance():
return balance
return deposit, withdraw, get_balance
# Create account with private state
deposit, withdraw, check_balance = create_bank_account(100)
print(f"Initial: ${check_balance()}") # 100
print(f"After deposit $50: ${deposit(50)}") # 150
print(f"After withdraw $30: ${withdraw(30)}") # 120
📚 Scope Best Practices
Parameter Passing vs Global Variables
Prefer passing values as parameters rather than using global variables for better testability and clarity.
# Poor: Using global variables
current_user = "Alice"
shopping_cart = []
def add_to_cart_global(item):
"""Bad: Depends on global variables"""
shopping_cart.append(f"{item} for {current_user}")
# Better: Using parameters
def add_to_cart_param(cart, user, item):
"""Good: Clear dependencies through parameters"""
cart.append(f"{item} for {user}")
return cart
# Clear, testable function calls
user_cart = []
user_cart = add_to_cart_param(user_cart, "Alice", "laptop")
user_cart = add_to_cart_param(user_cart, "Alice", "mouse")
print("Cart:", user_cart)
Avoiding Name Conflicts
Use descriptive names and proper scoping to prevent variable conflicts between different parts of your code.
def process_user_data(data):
"""Function with clear local scope"""
# Local variables with descriptive names
user_name = data.get('name', '').strip()
user_email = data.get('email', '').lower()
# Process data locally
if user_name and user_email:
processed_data = {
'name': user_name,
'email': user_email,
'status': 'valid'
}
else:
processed_data = {'status': 'invalid'}
return processed_data
# No naming conflicts - each function call is independent
user1 = process_user_data({'name': ' Alice ', 'email': 'ALICE@EMAIL.COM'})
user2 = process_user_data({'name': '', 'email': 'bob@email.com'})
print("User 1:", user1)
print("User 2:", user2)
Hands-on Exercise
Create a simple counter using a global variable. Make functions to increment the counter, get the current count, and reset the counter to zero.
# TODO: Create a global counter variable
counter = 0
def increment_counter():
# TODO: Use global keyword and increment the counter
pass
def get_count():
# TODO: Return the current counter value
pass
def reset_counter():
# TODO: Use global keyword and reset counter to 0
pass
# TODO: Test your functions
increment_counter()
increment_counter()
print(f"Count: {get_count()}")
reset_counter()
print(f"After reset: {get_count()}")
Solution and Explanation 💡
Click to see the complete solution
# Create a global counter variable
counter = 0
def increment_counter():
# Use global keyword and increment the counter
global counter
counter += 1
def get_count():
# Return the current counter value (no global needed for reading)
return counter
def reset_counter():
# Use global keyword and reset counter to 0
global counter
counter = 0
# Test your functions
increment_counter()
increment_counter()
print(f"Count: {get_count()}")
reset_counter()
print(f"After reset: {get_count()}")
Key Learning Points:
- 📌 Global variables: Declared outside functions, accessible everywhere
- 📌 global keyword: Required to modify global variables inside functions
- 📌 Reading vs writing: Can read globals without keyword, but need
global
to modify - 📌 Function scope: Each function has its own local scope for variables
Learn more about anonymous functions to discover Python's lambda expressions and their unique scoping behavior.
Test Your Knowledge
Test what you've learned about variable scope:
What's Next?
Congratulations! You've completed the fundamentals of Python functions. You now understand how to create functions, work with parameters, return values, and manage variable scope. These concepts form the foundation for more advanced programming topics.
Ready to explore more? Check out our lesson on Anonymous Functions to learn about lambda expressions.
Was this helpful?
Track Your Learning Progress
Sign in to bookmark tutorials and keep track of your learning journey.
Your progress is saved automatically as you read.