Scope and flow of execution
Part of the free LogicWiz Python course, Module 3: Functions. The examples and practice run in the app.
Meher kept the day's sales in sales = 0 and wrote add_sale() to do sales = sales + 50. It crashed with UnboundLocalError. The function could read sales a minute ago. Why can't it change it?
You'll learn to
- Tell local names from global names
- Explain UnboundLocalError and fix it
- Use `global` when you must, and prefer returning a value
- Follow the flow of execution through function calls
Learn
Each call gets its own notebook
When a function runs, Python opens a fresh local scope for it, like a rough notebook for that one call. Parameters and any name assigned inside the function live there. When the function returns, the notebook is thrown away.
Names made at the top level of the file are global: every function can read them.
Reading a global is fine
rate = 5
def with_gst(amount):
tax = amount * rate / 100
return amount + tax
print(with_gst(200))
print(rate)
Step through: amount and tax appear while with_gst runs and vanish after it returns. rate is global and stays.
Locals don't leak out
def with_gst(amount):
tax = amount * 5 / 100
return amount + tax
print(with_gst(200))
print(tax)
NameError: name 'tax' is not defined. tax belonged to that one call and is gone.
Assigning inside makes a new local
x = 10
def change():
x = 99
print("inside:", x)
change()
print("outside:", x)
x = 99 inside the function creates a local x. The global x is untouched: same name, two different boxes.
Meher's crash
sales = 0
def add_sale(amount):
sales = sales + amount
return sales
add_sale(50)
Why UnboundLocalError? Python decides which names are local before the function runs: any name assigned anywhere in the function is local for the whole function. So in sales = sales + amount, the right side reads the local sales, which has no value yet. sales += amount fails the same way.
Two fixes: global, or return
sales = 0
def add_sale(amount):
global sales
sales = sales + amount
add_sale(50)
add_sale(30)
print(sales)
def add_sale2(total, amount):
return total + amount
total = 0
total = add_sale2(total, 50)
total = add_sale2(total, 30)
print(total)
global sales says "use the file's sales". It works, but the second style (pass in, return out) is easier to test and reuse. Use global sparingly.
Flow of execution: calls stack up
def tax(x):
print(" tax starts")
return x * 0.05
def bill(x):
print(" bill starts")
t = tax(x)
print(" bill ends")
return x + t
print("main starts")
print(bill(100))
print("main ends")
Python reads the two defs first (just remembering them), then runs from print("main starts"). A call pauses the caller; return resumes it exactly where it left off. That pile of paused calls is the call stack.
CBSE asks flow of execution as a list of line numbers. The rule: the def header line is visited once when defined; on each call, jump to the header, run the body, then come back to the calling line. Number the lines in the margin and follow your pencil.
In one line
Names assigned inside a function are local to that call; reading a global is fine, assigning to it needs global (or better, return the new value).