Module 1: Values, Variables, and Expressions
Names and Expressions: Binding, Rebinding, and Order of Operations
What assignment actually does, why rebinding a name never changes values computed earlier, and how Python decides the order of an arithmetic expression.
Lesson 5 of 46 in the recommended order · About 25 min (estimate)
On this page
Outcome
By the end of this lesson you can read a run of assignments and say exactly what every name holds at every line, and you can predict what an arithmetic expression produces without running it.
Why it matters
"It should have updated" is one of the most common wrong beliefs in beginner code, and it comes from thinking of a name as a live link to a calculation. It is not. Assignment stores a result, once, and that result does not follow later changes to the things it was computed from.
Getting this right early removes a whole family of bugs that are invisible in a code review, because the code looks fine and the ordering is the problem.
Concept
name = expression does two things, in this order: it evaluates the expression on the right into a single value, then it binds the name on the left to that value. Nothing is remembered about how the value was produced.
Rebinding a name later replaces what the name points at. It does not reach backwards. If total was computed from base on line 3, changing base on line 5 leaves total exactly as it was.
The arithmetic operators are mostly what you would expect, with three worth memorising:
/always produces afloat, even when it divides evenly:10 / 5is5.0, not5.//is floor division: it divides and discards the fraction, keeping the type integral for two integers.7 // 2is3.%is the remainder:7 % 2is1. It is how you ask "does this divide evenly".
Precedence follows ordinary maths: ** first, then *, /, //, %, then + and -. Parentheses override everything, and using them where a reader might hesitate is not a weakness. base + base * rate and (base + base) * rate are different programs, and only one of them is what you meant.
Augmented assignment, total += 500, is shorthand for total = total + 500. It reads the current value, computes, and rebinds, all on one line.
Read the code
base_value = 120000
option_years = 2
option_rate = 0.4
option_value = base_value * option_rate * option_years
ceiling = base_value + option_value
option_rate = 0.6
print(f"Option value: {option_value:,.0f}")
print(f"Ceiling: {ceiling:,.0f}")
Read it as a sequence of moments. After line 3, three names hold three numbers. Line 5 evaluates 120000 * 0.4 * 2 to 96000.0 and binds option_value to that number. Line 6 evaluates 120000 + 96000.0 to 216000.0 and binds ceiling.
Line 8 rebinds option_rate to 0.6. Both option_value and ceiling were computed two lines earlier and are untouched by it.
Three moments in the same run
1. After line 3
base_value- 120000
option_years- 2
option_rate- 0.4
option_value- Not yet bound
ceiling- Not yet bound
Inputs are bound; the calculations have not run.
Next step in this same run
2. After line 6
base_value- 120000
option_years- 2
option_rate- 0.4
option_value- 96000.0
ceiling- 216000.0
Lines 5 and 6 bind the calculated values.
Next step in this same run
3. After line 8
base_value- 120000
option_years- 2
option_rate- 0.6
option_value- 96000.0
ceiling- 216000.0
Line 8 assigns option_rate: 0.4 → 0.6. The two calculated values stay the same.
Predict the output
Predict both printed lines exactly, including the thousands separators.
Check your prediction
Option value: 96,000
Ceiling: 216,000
If you predicted 144,000 and 264,000, you applied the new option_rate. That is the belief this lesson exists to correct: the rebinding on line 8 happens after both values were already computed and stored, so it changes nothing that was printed.
The :,.0f format spec asks for thousands separators and zero decimal places, which is why a float prints without a visible .0.
Modify the code
Move the line option_rate = 0.6 so it sits immediately after option_years = 2, before anything is computed. Predict both printed lines before you read on.
What changes, and why
Option value: 144,000
Ceiling: 264,000
Nothing about the arithmetic changed; only the moment the rate was bound. 120000 * 0.6 * 2 is 144000.0, and the ceiling follows. The whole difference between this run and the previous one is one line's position, which is exactly why "read the order, not just the lines" is a real reviewing skill.
Debug the bug
An assistant was asked for the total cost of a base year plus a ten percent contingency. It produced this and said it prints 132000.
base_value = 120000
contingency_rate = 0.1
total = base_value + base_value * contingency_rate
total = round(total)
total + 0
print(f"Total: {total}")
What's actually wrong
Two separate problems, and only one of them is what the assistant claimed.
The arithmetic is right: * binds tighter than +, so this is 120000 + 12000.0, which rounds to 132000. The printed number is correct.
The real defect is line 6, total + 0. It computes a value and throws it away, because nothing is assigned to. It is not an error, Python evaluates the expression and discards the result, so it runs silently and does nothing forever. Lines like this are a common residue of generated or half-edited code, and they matter: the next reader assumes a line that exists does something, and wastes time working out what.
If the intent had been to change total, the line would need to be total = total + 0 or total += 0. Delete it instead.
Try it yourself
A listing publishes a total contract value and a duration, both as text. Print the average value per month, rounded to two decimal places.
Loading this exercise…
Practical challenge (optional)
Optional: add an option-year calculation to your program. Introduce a rate and a number of option years, compute the ceiling value the way Read the Code did, and print the base, the option value, and the ceiling as three aligned lines. Then deliberately rebind the rate at the bottom of the file and confirm that nothing printed changes. Proving the rule to yourself once is worth more than reading it three times.
Sign in to track your progress on this exercise.
AI collaboration
Checkpoint
Given this sequence, what does each print show?
count = 4
count += 2
doubled = count * 2
count = 10
print(count)
print(doubled)
print(count % 3)
Answers
10, then 12, then 1.
count becomes 6 after the augmented assignment, so doubled is bound to 12. Rebinding count to 10 afterwards has no effect on doubled. 10 % 3 is 1, the remainder after dividing ten by three.
Sign in to track your progress on this exercise.
Summary and next step
Assignment evaluates then binds, rebinding never reaches backwards, / always yields a float, and parentheses are cheap insurance. Next: turning stored values into output a person can actually read, and the string operations that make published data consistent enough to compare.