Scope
Scope decides where a name can be seen. Variables created inside a function are local to it; variables at the top of a file are global to that module. Python looks names up using the LEGB rule, and the global and nonlocal keywords let you change variables in outer scopes when you really need to.
Local and global
greeting = "Hello" def welcome(name): message = f"{greeting}, {name}!" return message print(welcome("Ana")) print(greeting) print("message" in globals())
Hello, Ana! Hello False
message exists only while welcome runs — it's local. greeting is global (defined at module level) and can be read inside the function.
The LEGB rule
When Python meets a name, it searches four scopes in order and uses the first match:
- Local — inside the current function.
- Enclosing — inside any functions that contain the current one.
- Global — the module's top level.
- Built-in — names like
print,len,range.
x = "global" def outer(): x = "enclosing" def inner(): x = "local" print("inner sees:", x) inner() print("outer sees:", x) outer() print("module sees:", x) print(len("built-in lookup"))
inner sees: local outer sees: enclosing module sees: global 15
Assigning makes a name local
If a function assigns to a name anywhere in its body, Python treats that name as local for the whole function — even lines before the assignment:
counter = 0
def increment():
counter += 1
increment()
Traceback (most recent call last):
File "main.py", line 6, in <module>
increment()
~~~~~~~~~^^
File "main.py", line 4, in increment
counter += 1
^^^^^^^
UnboundLocalError: cannot access local variable 'counter' where it is not associated with a valueBecause of counter += 1, counter is local to increment, and reading it before it has a value fails.
global
counter = 0 def increment(): global counter counter += 1 increment() increment() print(counter)
2
global counter tells Python to use the module-level variable. It works, but functions that modify globals are hard to test and reason about. Usually it's better to pass values in and return results:
def increment(counter): return counter + 1 count = 0 count = increment(count) count = increment(count) print(count)
2
nonlocal
nonlocal refers to a variable in an enclosing function, which is how closures keep state:
def make_counter(): count = 0 def increment(): nonlocal count count += 1 return count return increment clicks = make_counter() clicks() clicks() print(clicks()) other = make_counter() print(other())
3 1
The inner function remembers count even after make_counter has returned — that's a closure. Each call to make_counter creates an independent counter.
Blocks don't create scope
Unlike many languages, if, for, while and with blocks don't have their own scope in Python. A variable created inside them is visible afterwards in the same function:
for i in range(3): last_square = i * i if True: flag = "set inside if" print(i, last_square, flag)
2 4 set inside if
Only modules, functions, classes and comprehensions create new scopes.
Shadowing built-ins
list = [1, 2, 3] numbers = list(range(3))
Traceback (most recent call last):
File "main.py", line 2, in <module>
numbers = list(range(3))
TypeError: 'list' object is not callableThe global name list now hides the built-in list type. Avoid naming variables list, dict, str, id, sum, input and other built-ins.