In Python, variable resolution follows the LEGB rule: Local, Enclosing, Global, Built-in. This systematic search order ensures predictable behavior, but sometimes we need to explicitly instruct the interpreter to modify a variable from a non-local scope rather than create a new local one. This is where the global and nonlocal declarations become essential. They are explicit statements that break the default read-only relationship with variables in outer scopes, allowing assignment operations to affect those specific namespaces directly.

The global Declaration

The global keyword is used to declare that a variable inside a function’s local scope refers to a name in the module’s global scope. Without this declaration, assigning a value to a name inside a function creates a new local variable, even if a global variable of the same name exists. The global statement tells the Python interpreter to bind the name’s assignment and subsequent lookups to the global namespace.

count = 0  # This is a global variable

def increment_counter():
    global count  # Declare 'count' as global
    count += 1    # Now this modifies the global 'count'
    print(f"Inside function: {count}")

increment_counter()
print(f"Outside function: {count}")  # Output: Outside function: 1

Contrast this with the behavior without the global declaration, which demonstrates a common pitfall:

count = 0

def try_increment():
    # Without 'global', this next line is interpreted as creating a new local variable.
    # Since 'count' is referenced on the right-hand side before assignment, it causes an error.
    count = count + 1  # UnboundLocalError: local variable 'count' referenced before assignment

try_increment()

The global declaration is required for both modifying mutable objects in-place and rebinding the name itself. However, for mutable objects like lists, you can modify their contents without a global declaration because you are not reassigning the name; you are operating on the object it references.

my_list = [1, 2, 3]  # Global list

def append_to_list():
    my_list.append(4)  # This works! Modifying the object, not rebinding the name.
    # my_list = [5, 6]  # This would require 'global' as it rebinds the name.

append_to_list()
print(my_list)  # Output: [1, 2, 3, 4]

The nonlocal Declaration

The nonlocal keyword is used in nested functions (closures) to declare that a variable refers to a name in the nearest enclosing scope that is not global. It allows an inner function to modify a variable from an outer (but non-global) scope. This is crucial for creating closures that maintain and update state.

def outer_function():
    value = "Original"  # Enclosing (non-global) scope variable

    def inner_function():
        nonlocal value  # Declare 'value' as nonlocal
        value = "Modified"  # This modifies the enclosing scope's 'value'

    inner_function()
    print(value)  # Output: Modified

outer_function()

Without nonlocal, the inner function would create a new local variable named value, leaving the enclosing scope’s variable unchanged—a subtle and often confusing bug.

def outer_function():
    value = "Original"

    def inner_function():
        value = "New Local"  # This creates a new local variable, shadowing the outer one.

    inner_function()
    print(value)  # Output: Original (The outer variable was not changed)

outer_function()

Key Differences and Best Practices

It is critical to understand the distinction between global and nonlocal. global always references the top-most module-level scope. nonlocal references the nearest enclosing scope, which could be several levels deep in a nested function chain. A nonlocal variable must already exist in an enclosing scope; you cannot create a new one with nonlocal.

x = "global"

def outer():
    x = "nonlocal"

    def inner():
        nonlocal x  # Refers to the 'x' in outer()
        x = "changed nonlocal"

        # global x  # This would refer to the module-level 'x', not outer()'s.
        # x = "changed global"

    inner()
    print(x)  # Output: changed nonlocal

outer()
print(x)      # Output: global (the module-level variable remains unchanged)

Common Pitfalls and Best Practices:

  1. Clarity Over Cleverness: Overusing global can lead to code that is difficult to debug and reason about, as any part of the program can change the variable’s state. It often indicates a design that might be better served by passing arguments and returning values or using class attributes to manage state.
  2. nonlocal for Stateful Closures: The primary legitimate use case for nonlocal is in closures that need to remember and update state between calls, such as decorators or function factories.
  3. Declaration Before Use: Both global and nonlocal must be declared before the name is used in the scope. They are directives to the compiler about how to handle the name throughout the entire code block.
  4. No New nonlocal Variables: You cannot use nonlocal to create a new variable in an enclosing scope; the name must already exist there. Attempting to do so results in a SyntaxError.