Scope determines where in your code a variable can be seen and used. Python resolves this with a simple rule known as LEGB — Local, Enclosing, Global, Built-in — meaning it looks for a name first inside the current function, then in any enclosing function, then at the module level, and finally among Python's built-ins, stopping at the first match it finds.
Local vs. Global Scope
count = 0 # global variable
def increment():
count = count + 1 # ERROR
return count
increment()
Output:
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
This surprises a lot of beginners: Python sees the assignment count = count + 1 inside the function and decides count is a local variable for the entire function body — so when it tries to read count on the right-hand side before it's been locally assigned, it fails. Simply reading a global variable works fine; the problem only appears once you try to assign to a name that shares a global variable's name.
The global Keyword
count = 0
def increment():
global count
count = count + 1
return count
print(increment())
print(increment())
print(count)
Output:
1
2
2
Declaring global count tells Python explicitly: "don't create a new local variable — modify the module-level one." Use this sparingly; functions that quietly mutate global state are harder to reason about than ones that take input and return output.
The nonlocal Keyword in Nested Functions
def make_counter():
count = 0 # enclosing scope
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter())
print(counter())
print(counter())
Output:
1
2
3
nonlocal is the equivalent of global, but for an enclosing function's scope rather than the module's global scope — it's what makes closures like make_counter() able to remember and update state between calls. This pattern is also central to how decorators work under the hood.
