Scope in Python: where a variable lives
You set total inside a function, and the total outside doesn't budge. That isn't a bug in Python. It's scope, and it's what stops functions from trampling each other's data.
The big idea
A variable assigned inside a function is local: it exists only while that call runs and is invisible outside it.
See it in code
profit is created inside the function and disappears when the call ends. The only thing that survives is what you return:
def profit_per_tee(price, cost):
profit = price - cost
return profit
answer = profit_per_tee(38, 14)
print("Returned:", answer)Returned: 24
Outside the function there is no profit; asking for it would raise a NameError. The value escaped only because it was returned and bound to answer.
Scope works one way. A function can read a variable defined outside it, which is handy for settings shared across the program:
clearance = 10
def leftover_value(unsold):
return unsold * clearance
print("Value of 120 unsold tees: $", leftover_value(120))Value of 120 unsold tees: $ 1200
clearance isn't a parameter or a local, so Python looked outward and found it at the top level. Reading outward is allowed.
Assigning is different. An assignment inside a function creates a new local with that name, and leaves the outer one untouched:
total_sold = 0
def record_sale(quantity):
total_sold = quantity
print("inside the function:", total_sold)
record_sale(12)
print("outside the function:", total_sold)
def new_total(current, quantity):
return current + quantity
total_sold = new_total(total_sold, 12)
print("after returning the result:", total_sold)inside the function: 12 outside the function: 0 after returning the result: 12
The first function changed its own private total_sold, then threw it away. The second takes the current value in, returns a new one, and lets the caller decide where it goes.
Local scope is why you can use i, total, or result in fifty functions without any of them colliding. It's also why well-written functions are easy to test: everything they need comes in through parameters.
Try it yourself
Rename the inner variable in record_sale to sale_amount. The behavior doesn't change at all, which proves the two names were never connected. Then make record_sale return a value instead of printing.
The common mistake
Reaching for global to make the first version work. It does work, but now any function can change total_sold and you can't tell which one did. Pass values in, and return results out.
What it unlocks
Scope is the reason return values matter. When several functions genuinely need to share changing data, bundle it into an object with classes.