Skip to content

How to Access Variables defined outside of Scope in Python

Learn how to read and modify external variables in Python using global, nonlocal, and closures while following the LEGB scope rule.

In Python, accessing variables defined outside of the current function (external variables) depends entirely on Scope. While Python allows you to read global or enclosing variables easily, modifying them requires specific keywords and an understanding of the LEGB rule (Local, Enclosing, Global, Built-in).

This guide explains how to properly access and modify external variables using global, nonlocal, and closures.

Understanding the Scope Hierarchy (LEGB)

Python resolves variables in a specific order known as LEGB:

  1. Local: Inside the current function.
  2. Enclosing: Inside enclosing functions (if nested).
  3. Global: At the top level of the module.
  4. Built-in: Predefined Python keywords (e.g., len, print).

You can read a global variable inside a function without any special syntax, but you cannot modify it without declaring your intent.

x = 10  # Global variable

def read_global():
    print(f"Reading global x: {x}")

read_global()

Output:

Reading global x: 10

Method 1: Modifying Module-Level Variables (global)

If you attempt to modify a global variable inside a function (e.g., x += 1), Python assumes x is a local variable that hasn't been defined yet, raising an UnboundLocalError. To fix this, use the global keyword.

counter = 0  # Global variable

def increment_fail():
    try:
        counter += 1 
    except UnboundLocalError as e:
        print(f"Error: {e}")

def increment_success():
    global counter
    counter += 1
    print(f"Counter is now: {counter}")

increment_fail()
increment_success()

Output:

Error: local variable 'counter' referenced before assignment
Counter is now: 1

Method 2: Modifying Nested Variables (nonlocal)

When working with nested functions (a function inside another function), the inner function can access variables from the outer function (Enclosing scope). To modify them, use the nonlocal keyword.

def outer_function():
    count = 0  # Enclosing variable (Local to outer, Nonlocal to inner)

    def inner_fail():
        count = 10 
    
    def inner_success():
        nonlocal count
        count += 5
    
    inner_success()
    print(f"Outer count value: {count}")

outer_function()

Output:

Outer count value: 5

Method 3: Using Closures (State Retention)

A cleaner alternative to using global variables for maintaining state (like a counter) is using a Closure. A closure allows a function to "remember" the environment in which it was created.

def create_multiplier(factor):
    def multiplier(x):
        return x * factor
    return multiplier

double = create_multiplier(2)
triple = create_multiplier(3)

print(f"Double 5: {double(5)}")
print(f"Triple 5: {triple(5)}")

Output:

Double 5: 10
Triple 5: 15

Common Pitfall: Mutable Default Arguments

Sometimes, external variables aren't the issue, but rather how Python handles default arguments. Default arguments are evaluated only once when the function is defined, not every time it is called. This creates a persistent variable across calls.

def append_bad(value, lst=[]):
    lst.append(value)
    return lst

print(append_bad(1)) 
print(append_bad(2)) # Unexpected: [1, 2]

def append_good(value, lst=None):
    if lst is None:
        lst = [] # Create a new local list every time
    lst.append(value)
    return lst

print(append_good(1))
print(append_good(2)) # Expected: [2]

Output:

[1]
[1, 2]
[1]
[2]

Conclusion

To manage variable access effectively in Python:

  1. Reading: You can read Global or Enclosing variables directly without special syntax.
  2. Writing Global: Use the global keyword to modify variables defined at the module level.
  3. Writing Enclosing: Use the nonlocal keyword to modify variables defined in an outer function.
  4. Best Practice: Prefer passing arguments and returning values or using Closures over relying heavily on global state modifications.
Navigation