How the Python Global Keyword Affects Function Scope
Explains how the python global keyword changes name resolution inside functions, when it is required, and where nonlocal or return values are better alternatives.
The python global keyword declares that a name inside a function refers to a module-level variable. Without it, any assignment to that name inside the function creates a local variable, even when a module-level variable with the same name already exists. This single rule is the source of most confusion around global.
How Python Resolves Names Inside Functions
Python uses lexical scoping to resolve names. When a function executes, the interpreter looks for a name in this order: local scope, enclosing function scopes, module (global) scope, and builtins. Reading a module-level variable works without any declaration:
counter = 0 def read_counter(): return counter print(read_counter()) # 0
The function finds counter in the module scope because no local binding exists. Assignment changes the picture. If a function assigns to a name anywhere in its body, Python treats that name as local for the entire function. The following code raises UnboundLocalError:
counter = 0 def increment(): counter = counter + 1 # UnboundLocalError: local variable 'counter' referenced before assignment increment()
The right-hand side reads counter before the local assignment completes. Python's compiler already decided counter is local, so the read fails instead of falling back to the module-level value.
The Syntax and What It Changes
A global declaration tells the compiler that the listed names refer to module-level bindings for the whole function body:
counter = 0 def increment(): global counter counter += 1 increment() print(counter) # 1
The global statement must appear before any use of the name in the function. It is not an assignment; it is a declaration that changes how the compiler treats the name. After the declaration, both reads and writes target the module-level variable.
Reading Without Declaring
Reading a module-level variable does not require global. Python falls back to the global scope when a name is not found in the local or enclosing scopes. The asymmetry between reading and writing is the most common source of confusion:
limit = 100 def check(value): return value < limit # reads module-level limit, no declaration needed
The moment you add an assignment, the name becomes local. This behavior is why a function that reads a global on one line and assigns to it on another fails at the read:
limit = 100 def update(value): print(limit) # UnboundLocalError limit = value
The compiler sees the assignment later in the body and marks limit as local for the entire function.
What global Does Not Do
global does not create the variable. If the module-level name does not exist when the function runs, the assignment still creates a module-level binding:
def create(): global new_name new_name = 5 create() print(new_name) # 5
This works, but it is usually a sign that the module structure is unclear. global also does not affect nested functions. A function nested inside another function still needs its own global declaration to reach module scope. And global never reaches into an enclosing function's local scope; that requires nonlocal.
Common Pitfall: Forgetting the Declaration
Forgetting global is the most frequent mistake. The function silently creates a local variable and the module-level value never changes:
value = 10 def update(): value = 20 # local, no error update() print(value) # 10
There is no warning. The code runs, the module-level value stays at 10, and the bug appears only when another part of the program reads the old value. Adding global value to the function body fixes the behavior.
Nested Functions and the Nonlocal Alternative
Inside a nested function, global refers to the module scope, not the enclosing function's scope. To modify a variable from the enclosing function, use nonlocal:
def outer(): count = 0 def inner(): nonlocal count count += 1 inner() return count print(outer()) # 1
nonlocal works like global but targets the nearest enclosing function scope. The two keywords solve different problems, and using global inside a nested function when you meant nonlocal is a common error.
When Using Global Is Justified
Pure functions are easier to test and reason about, but global has legitimate uses:
- Module-level configuration values that are read in many places
- Simple counters in small scripts
- Caching a single computed value that is expensive to recalculate
For most other cases, returning the new value is cleaner:
def increment(counter): return counter + 1 counter = 0 counter = increment(counter)
Mutable containers offer another path. You can mutate a list or dict without global because you are not rebinding the name:
state = {"count": 0} def increment(): state["count"] += 1 increment() print(state["count"]) # 1
This works because state is only read, and the object is modified in place. The approach still carries the same maintainability concerns as global, but it avoids the declaration.
Maintainability and Runtime Considerations
The runtime cost of global itself is negligible; the interpreter resolves the name against the module dictionary. The real cost is organizational. A function that mutates module-level state depends on the current value of that state, which makes it harder to test in isolation and harder to reason about when multiple call sites invoke it.
Threading adds another concern. global provides no synchronization. Two threads calling a function that increments a global counter can interleave and lose updates. If concurrent access is possible, protect the shared variable with a lock or use a thread-safe data structure.
The behavior of global is stable across all supported Python versions. There is no version-specific syntax difference for the basic use. The main compatibility concern is code style: overuse of global makes modules harder to refactor, so most codebases restrict it to configuration and caching patterns.