Scope decides where a name can be found and changed in Python code.
Python searches names using the LEGB idea: local, enclosing, global, then built-in.
Most scope bugs happen when a variable is created in one place and read from another place where Python cannot see it.
Scope determines where a variable is accessible. Python uses the LEGB rule to resolve variable names - it searches in this order:
def my_function():
x = 10 # local variable - only exists inside this function
print(x)
my_function() # 10
# print(x) # NameError: name 'x' is not defined
# Each function call gets its own local scope
def counter():
count = 0
count += 1
return count
print(counter()) # 1
print(counter()) # 1 (fresh scope each call)
message = "Hello" # global variable
def greet():
print(message) # can READ global variable
greet() # Hello
# To MODIFY a global variable inside a function, use 'global'
count = 0
def increment():
global count
count += 1
increment()
increment()
print(count) # 2
# Without 'global', Python creates a new local variable
def bad_increment():
count = count + 1 # UnboundLocalError!
When a nested function references a variable from its enclosing function, it creates a closure.
def outer(x):
def inner(y):
return x + y # 'x' is from enclosing scope
return inner
add5 = outer(5)
add10 = outer(10)
print(add5(3)) # 8
print(add10(3)) # 13
# Closure remembers the enclosing scope
def make_counter():
count = 0
def increment():
nonlocal count # modify enclosing variable
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
# Each call to make_counter() creates an independent counter
counter2 = make_counter()
print(counter2()) # 1 (independent)
def outer():
x = 10
def inner():
nonlocal x # refers to outer's x
x = 20
print(f"inner: x = {x}")
inner()
print(f"outer: x = {x}") # x was modified by inner
outer()
# inner: x = 20
# outer: x = 20
# LEGB in action
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local
inner()
print(x) # enclosing
outer()
print(x) # global
import builtins
# See all built-in names
print(dir(builtins))
# Built-ins are always available
print(len([1, 2, 3])) # 3
print(type("hello")) # <class 'str'>
print(range(5)) # range(0, 5)
# You can shadow built-ins (but don't!)
# list = [1, 2, 3] # now 'list' is a variable, not the built-in!
# list([1, 2]) # TypeError!
# Check if a name is a built-in
print(hasattr(builtins, "print")) # True
print(hasattr(builtins, "myvar")) # False
Try this next
0 of 3 completed
LEGB means local, enclosing, global, and built-in. Python checks names in that order.
Use global rarely. Passing values as parameters and returning results is usually clearer and easier to test.
nonlocal lets an inner function change a variable from the nearest enclosing function scope.
Explore 500+ free tutorials across 20+ languages and frameworks.