Python Global, Local, and Nonlocal Variables
Variable scope determines where in your code a variable can be accessed and modified. Understanding scope is fundamental to writing Python code that behaves predictably and avoids subtle bugs. When...
Key Insights
- Python resolves variable names using the LEGB rule (Local, Enclosing, Global, Built-in), which determines which scope takes precedence when multiple variables share the same name.
- The
globalkeyword allows functions to modify module-level variables, whilenonlocalenables nested functions to modify variables from their enclosing scope—but both should be used sparingly. - Local variables are the safest choice for most programming tasks; excessive use of global variables creates hidden dependencies that make code harder to test, debug, and maintain.
Introduction to Variable Scope
Variable scope determines where in your code a variable can be accessed and modified. Understanding scope is fundamental to writing Python code that behaves predictably and avoids subtle bugs. When you reference a variable name, Python needs to know which variable you mean—the one defined in the current function, one from an outer function, or perhaps a module-level variable.
Python uses three primary scope types: local (variables within functions), global (module-level variables), and nonlocal (variables in enclosing function scopes). Each serves a specific purpose, and choosing the right scope prevents common errors like unintended variable modification or namespace pollution.
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(f"Inner x: {x}")
inner()
print(f"Outer x: {x}")
outer()
print(f"Global x: {x}")
# Output:
# Inner x: local
# Outer x: enclosing
# Global x: global
This example shows three different variables named x, each existing in its own scope without interfering with the others.
Local Variables
Local variables are defined inside functions and exist only during function execution. They’re created when the function is called and destroyed when it returns. This isolation is a feature, not a limitation—it prevents functions from accidentally interfering with each other’s data.
def calculate_total(price, quantity):
tax_rate = 0.08 # Local variable
subtotal = price * quantity # Local variable
total = subtotal * (1 + tax_rate) # Local variable
return total
result = calculate_total(100, 3)
print(result) # 324.0
# This will raise NameError: name 'tax_rate' is not defined
# print(tax_rate)
Local scope provides encapsulation. Each function invocation gets its own set of local variables, even if the same function is called recursively:
def countdown(n):
count = n # Each call gets its own 'count'
print(count)
if count > 0:
countdown(count - 1)
countdown(3)
# Output: 3, 2, 1, 0
Two functions can use identical variable names without conflict because their local scopes are isolated:
def calculate_area(radius):
pi = 3.14159
result = pi * radius ** 2
return result
def calculate_circumference(radius):
pi = 3.14159 # Different variable, same name
result = 2 * pi * radius
return result
# Both 'pi' and 'result' variables coexist peacefully
print(calculate_area(5))
print(calculate_circumference(5))
Global Variables
Global variables are defined at the module level and can be accessed from anywhere in the module. You can read global variables inside functions without any special syntax, but modifying them requires the global keyword.
app_name = "MyApp"
version = "1.0"
def print_info():
# Reading global variables works without 'global' keyword
print(f"{app_name} v{version}")
print_info() # MyApp v1.0
To modify a global variable from within a function, you must declare it with the global keyword:
counter = 0
def increment():
global counter # Required to modify global variable
counter += 1
increment()
increment()
print(counter) # 2
A common mistake is forgetting the global keyword when trying to modify a global variable. Without it, Python creates a new local variable instead:
total = 0
def add_to_total(amount):
# This creates a LOCAL variable named 'total', doesn't modify global
total = total + amount # UnboundLocalError!
# add_to_total(10) # Error: local variable 'total' referenced before assignment
The error occurs because Python sees the assignment total = ... and decides total is local. When it tries to read total on the right side of the equation, the local variable doesn’t exist yet.
Global variables should be used sparingly. They’re appropriate for true application-level configuration or state, but overusing them creates hidden dependencies that make code difficult to test and maintain. Constants (by convention, UPPERCASE names) are acceptable as globals:
MAX_CONNECTIONS = 100
API_ENDPOINT = "https://api.example.com"
The Nonlocal Keyword
The nonlocal keyword addresses a specific scenario: nested functions that need to modify variables from their enclosing (but not global) scope. This is particularly useful for closures and decorators.
def outer():
count = 0
def inner():
# This would create a local variable, not modify outer's count
# count = count + 1 # UnboundLocalError
# nonlocal allows modifying the enclosing scope's variable
nonlocal count
count += 1
return count
return inner
counter = outer()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
Without nonlocal, attempting to modify count would create a local variable in inner, leading to an error. The nonlocal keyword tells Python to look in enclosing function scopes for the variable.
Here’s a practical example—a function that creates customized validators:
def create_validator(min_value, max_value):
validation_count = 0
def validate(value):
nonlocal validation_count
validation_count += 1
if value < min_value or value > max_value:
return False, f"Validation #{validation_count}: Out of range"
return True, f"Validation #{validation_count}: Valid"
return validate
age_validator = create_validator(0, 120)
print(age_validator(25)) # (True, 'Validation #1: Valid')
print(age_validator(150)) # (False, 'Validation #2: Out of range')
print(age_validator(30)) # (True, 'Validation #3: Valid')
The validation_count persists across calls to validate because it’s captured in the closure, and nonlocal allows the inner function to modify it.
LEGB Rule (Lookup Order)
When Python encounters a variable name, it searches for it in a specific order: Local, Enclosing, Global, Built-in (LEGB). It uses the first match it finds.
x = "global x"
def outer():
x = "enclosing x"
def inner():
x = "local x"
print(x) # Finds local x first
inner()
print(x) # Finds enclosing x (no local x here)
outer()
print(x) # Finds global x
# Output:
# local x
# enclosing x
# global x
Here’s a more complex example demonstrating the full LEGB resolution:
# Built-in scope: len() function exists here
x = "global" # Global scope
def outer():
x = "enclosing" # Enclosing scope
def inner():
x = "local" # Local scope
print(f"Local: {x}")
print(f"Built-in len available: {len([1, 2, 3])}")
def inner2():
# No local x, so Python finds enclosing x
print(f"Enclosing: {x}")
inner()
inner2()
outer()
Understanding LEGB helps debug confusing situations where variables don’t have the values you expect.
Best Practices and Common Pitfalls
Minimize Global Variable Usage: Prefer passing data through function parameters and return values. This makes dependencies explicit and functions easier to test.
# Bad: Hidden dependency on global variable
total = 0
def add_sale(amount):
global total
total += amount
# Good: Explicit parameters and return values
def add_sale(current_total, amount):
return current_total + amount
total = 0
total = add_sale(total, 100)
total = add_sale(total, 50)
Avoid Variable Shadowing: Don’t reuse names from outer scopes unless you have a good reason. It confuses readers and can cause bugs.
# Problematic: shadows built-in
def process_data(list): # 'list' shadows built-in type
# Now you can't use list() constructor
result = []
for item in list:
result.append(item * 2)
return result
# Better: use descriptive names
def process_data(data_list):
result = []
for item in data_list:
result.append(item * 2)
return result
Use Globals for Constants Only: If a value truly doesn’t change, a global constant is fine. For mutable state, consider classes or explicit state management.
# Acceptable: configuration constants
DATABASE_URL = "postgresql://localhost/mydb"
MAX_RETRIES = 3
# Better than globals: encapsulate state in a class
class ShoppingCart:
def __init__(self):
self.items = []
self.total = 0
def add_item(self, price):
self.items.append(price)
self.total += price
When to Use Each Scope:
- Local: Default choice for all function-internal data
- Global: Constants and truly application-wide configuration
- Nonlocal: Closures and decorators that maintain state across calls
Understanding Python’s scoping rules prevents bugs and makes your code more maintainable. Default to local variables, use globals sparingly for constants, and reach for nonlocal only when implementing closures or decorators. When in doubt, explicit is better than implicit—pass data through function parameters rather than relying on outer scopes.