Scope & Lifetime
Intermediate · 20 min read · ▶ live playground · ✦ checkpoint
You can change a name inside a function and still find the outside name untouched. That is not Python being sneaky; it is Python protecting each function's workspace with scope and lifetime rules.
Local names stay inside
Scope is the part of a program where Python can find a name. Lifetime is how long that name exists before Python can forget it.
Picture scopes as rooms with name tags on the walls. A function call opens a private room. Names created inside that room are local names, which means they belong to that function call.
mood = "nervous"
def calm_player():
mood = "focused"
print("inside:", mood)
calm_player()
print("outside:", mood)inside: focusedoutside: nervous
The two names are both called mood, but they live in different rooms. The assignment inside calm_player() creates a local name. The global mood outside the function keeps pointing to "nervous".
A local scope is the private scope created by a function call. When the call ends, its local names are no longer available. The values may live on if something else still refers to them, but the local name tags from that call are gone.
Global names live in the main room
A global scope is the top-level scope of a Python file, outside any function. Names you create there can be read from inside functions.
TEAM_BONUS = 2
def final_score(player, points):
total = points + TEAM_BONUS
return f"{player}: {total}"
print(final_score("Mina", 40))
print(final_score("Ravi", 35))Mina: 42Ravi: 37
Reading a global name is normal, especially for constants by convention like TEAM_BONUS. Writing to a name is different. A plain assignment inside a function creates or changes a local name unless you explicitly say otherwise.
score = 10
def add_points():
score = 15
print("inside:", score)
add_points()
print("outside:", score)inside: 15outside: 10
This rule keeps functions from accidentally moving name tags in the main room. A function can use a global value as input, but a plain assignment inside the function stays local.
LEGB is Python's search path
LEGB is Python's name lookup order: Local, Enclosing, Global, Built-in. When Python reads a name, it checks the current function first, then any surrounding function, then the global scope, then the built-in scope, which is Python's ready-made names such as len.
backup_snack = "apples"
def plan_trip():
day = "Friday"
def pack_bag():
snack = "mangoes"
print("local snack:", snack)
print("enclosing day:", day)
print("global backup:", backup_snack)
print("built-in length:", len(snack))
pack_bag()
plan_trip()local snack: mangoesenclosing day: Fridayglobal backup: applesbuilt-in length: 7
A nested function is a function defined inside another function. In this example, pack_bag() has its own local snack. It reads day from the enclosing plan_trip() room. It reads backup_snack from the global room. It also finds len in Python's built-in scope.
Use global and nonlocal carefully
The global statement tells Python that assignments to a name inside this function should rebind the global name. It is a statement about where the name lives.
team_score = 10
def add_team_points(points):
global team_score
team_score = team_score + points
add_team_points(5)
print("team:", team_score)
def play_round():
round_score = 0
def add_round_points(points):
nonlocal round_score
round_score = round_score + points
print("round:", round_score)
add_round_points(4)
add_round_points(3)
print("final round:", round_score)
play_round()team: 15round: 4round: 7final round: 7
The nonlocal statement tells Python that assignments should rebind a name in the nearest enclosing function scope. It does not reach the global scope. Here, add_round_points() changes round_score from play_round().
Use these words to read code confidently. Do not reach for them first. Most beginner functions are clearer when they take values as arguments and return new values.
Why global state hurts
Global state is global data that can change while the program runs. It feels convenient at first because every function can see it. It becomes painful because every function can depend on it.
discount = 0
def final_price(price):
return price - discount
print(final_price(20))
discount = 5
print(final_price(20))
def clear_final_price(price, discount):
return price - discount
print(clear_final_price(20, 0))
print(clear_final_price(20, 5))20152015
The first final_price(20) call and the second final_price(20) call look identical, but they return different values because a global name changed between them. That makes bugs harder to trace. It is like a shared scoreboard on the wall: if anyone can erase it, one line of code does not tell the whole story.
The clearer version passes discount as an argument. Now the inputs are visible in the call, and the function's result is easier to predict.
Try it - trace the rooms
Run this, then change team_bonus, the player's name, and the points added inside add_points(). Watch which names live globally and which live only during play_round().
team_bonus is global and only read. score is local to play_round(), and nonlocal score lets the nested add_points() function update that enclosing local name. The final print proves the global bonus never moved.
Checkpoint
Answer all three to mark this lesson complete
You now know where Python looks for names, how long function names last, and why hidden global state makes code harder to trust. Next, functions themselves become values you can pass around, store, and combine.