Functions_Comprehensions_and

1

Scope and Variable Visibility

▶

Understanding how Python finds and resolves variable names is fundamental to writing correct, predictable functions. Python follows a strict four-level hierarchy called the LEGB rule, and two special keywords — global and nonlocal — let functions reach into outer scopes when necessary.

  • The LEGB Scope Rule
    • Local scope contains names assigned within the current function. This is the first place Python looks when a name is referenced inside a function body.
    • Enclosing scope covers variables in any outer function when functions are nested. Python walks outward through each enclosing function in order, from innermost to outermost.
    • Global scope holds names defined at the top level of the module. These names are visible throughout the entire file unless shadowed by a more local definition.
    • Built-in scope contains Python's pre-defined names such as len, range, and print. This is the last place Python checks and is always available without any import.
    • Name resolution stops at the first scope where the name is found. If a name is discovered in the local scope, Python never inspects the enclosing, global, or built-in scopes for that same lookup.
  • Local Variables
    • Assignment inside a function creates a new local variable rather than modifying a global one. Even if a global variable with the same name exists, the assignment targets the local scope exclusively.
    • Local variables are created when the function is called and destroyed when it returns. They have no existence outside of a single function invocation, keeping memory usage efficient and state isolated.
    • Reading a local variable before assigning it raises an UnboundLocalError. This happens because Python determines at compile time that any name assigned anywhere in a function is local to that function, even if the assignment appears later in the code.
    • Local scope provides encapsulation, preventing accidental modification of outer variables. Functions that rely only on local variables and their parameters are easier to test and reason about in isolation.
  • The global Keyword
    • Declaring global variable_name inside a function binds that name to the module-level variable. Any subsequent assignment or mutation of that name then affects the global object directly.
    • Without global, assigning to a name inside a function always creates a local variable. This is a common source of confusion when a developer intends to update a global counter or flag but inadvertently creates a separate local binding instead.
    • The global keyword should be used sparingly to avoid hidden side effects. Functions that silently alter global state are harder to debug, test, and reuse because their behaviour depends on external context.
    • Accessing a global variable for reading does not require the global keyword. Python will naturally find the name in the global scope through normal LEGB lookup as long as no local assignment of the same name exists in the function.
  • The nonlocal Keyword
    • nonlocal binds a name to the nearest enclosing scope that contains that variable. Python searches outward through nested function layers until it finds the first enclosing function where the name is defined.
    • It enables nested functions to update, not just read, variables from an outer function. Without nonlocal, an assignment in the inner function would create a new local variable, leaving the outer function's variable unchanged.
    • nonlocal does not reach all the way to the global scope; use global for that. The two keywords are distinct tools for distinct situations — nonlocal is strictly for enclosing function scopes.
    • This keyword is essential for implementing closures that maintain mutable state. A classic example is a counter factory where the inner function increments a count variable defined in the outer function, preserving the value across multiple calls to the inner function.
# LEGB and keyword demonstration

x = "global"

def outer():
    x = "enclosing"

    def inner():
        nonlocal x          # binds to outer()'s x
        x = "modified by inner"
        print(x)            # modified by inner

    inner()
    print(x)                # modified by inner

outer()
print(x)                    # global  (unchanged)

# global keyword example
count = 0

def increment():
    global count
    count += 1

increment()
increment()
print(count)                # 2
NotesExplains how Python resolves variable names using the LEGB rule and how the global and nonlocal keywords allow functions to modify variables in enclosing scopes. Slides: 1, 2, 3, 4.