As programs grow larger, reusing common variable names like total, i, or data becomes inevitable. How does Python ensure that a variable inside one function does not accidentally overwrite an identical variable in another module? The answer lies in Namespaces and Variable Scopes.
Figure 29.1: Concentric hierarchy of Python's LEGB variable resolution: Local -> Enclosing -> Global -> Built-in.
Whenever Python encounters a variable name, it searches through four nested scopes in strict order:
• 1. Local (L): Names assigned within the current active function or lambda.
• 2. Enclosing (E): Names defined in any outer enclosing functions (closures).
• 3. Global (G): Names assigned at the top level of the current .py file/module.
• 4. Built-in (B): Preloaded names provided by Python (print, len, range, ZeroDivisionError).
global KeywordBy default, assigning to a variable inside a function creates a new local variable. To modify a top-level global variable from within a function, you must declare it with global:
counter = 0 # Global variable
def increment():
global counter # Tell Python to use the global variable
counter += 1
increment()
increment()
print("Final Counter:", counter) # 2
nonlocal Keyword (Nested Functions & Closures)The nonlocal keyword allows an inner function to modify a variable in the outer enclosing function's scope without polluting the global namespace:
def create_counter():
count = 0 # Enclosing scope
def step():
nonlocal count # Refers to count in create_counter
count += 1
return count
return step
my_counter = create_counter()
print(my_counter()) # 1
print(my_counter()) # 2
print(my_counter()) # 3
locals() and globals(). Both return standard Python dictionaries containing all currently active variable names and their object bindings.