Names, Values, and Assignment
Bind meaningful names to Python objects, tell identity apart from equality, and avoid the aliasing surprises that follow from both.
By the end of this lesson, you can
- Explain that assignment binds a name to an object rather than storing a value in a labeled box
- Distinguish identity (is) from equality (==), and explain what id() reports
- Predict aliasing behavior for chained and multiple assignment
- Recognize floating-point representation limits and know when to reach for decimal or fractions instead
Why it matters
Almost every early Python surprise — “why did changing one list change the other one?”, “why is
0.1 + 0.2 not 0.3?”, “why does is sometimes work like == and sometimes not?” — traces back
to one mental model being wrong. Fix the model here and a whole category of future bugs stops being
mysterious.
Mental model
Forget “a variable is a box that holds a value.” A name is a label attached to an object that
exists independently of the label. price = 19.99 doesn’t put 19.99 inside something called
price — it creates a float object and attaches the label price to it. price = 24.99 later
doesn’t change that object; it detaches the label and reattaches it to a new one.
This distinction only becomes visible once two names can label the same object — which is exactly what plain assignment does:
first = []
second = first
first.append("milk")
print(second)['milk']second never received a copy of first’s list — it’s a second label on the exact same object, so
a change made through first is visible through second too. This is normal, expected behavior,
not a bug — see Assignment for exactly which forms of assignment
share an object this way and which don’t.
Worked example: identity versus equality
Python has two different comparisons for a reason: == asks “do these have the same value?” and
is asks “are these the literal same object?” They usually give different answers.
def bill_total(prices: list[float], tax_rate: float) -> float:
subtotal = sum(prices)
return subtotal + subtotal * tax_rate
def main() -> None:
prices = [19.99, 5.5, 3.25]
print(f"Subtotal: ${sum(prices):.2f}")
print(f"Total with tax: ${bill_total(prices, tax_rate=0.08):.2f}")
a = [1, 2, 3]
b = [1, 2, 3]
print(f"a == b: {a == b}")
print(f"a is b: {a is b}")
if __name__ == "__main__":
main()Subtotal: $28.74
Total with tax: $31.04
a == b: True
a is b: Falsea and b hold equal contents but are two separate list objects built from two separate literal
expressions — a == b is True, a is b is False. id() is what
is checks under the hood, and it’s how you’d confirm this directly if you wanted to.
Common mistakes: floating-point limits
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)0.30000000000000004
FalseCheck your understanding
Or reveal the answer without checking
Answer:['a', 'b']
Chained assignment evaluates the right-hand side [] once and binds both first and second to that single object — they are two names for the same list, so both appends land on it.
Or reveal the answer without checking
Answer:Whether x and y happen to be the same object — which is not guaranteed here and should not be relied on
is checks object identity, not value. Two separately-created float literals are not guaranteed to be the same object (unlike some small integers, which CPython happens to cache as an implementation detail) — == is the correct tool for comparing values.
Practice: warm-up
Predict, then check in a REPL: after x = 10, y = x, x = x + 1, what are x and y now?
Explain in one sentence why y didn’t change, using the “label on an object” model rather than
the “box” model.
Stuck? Reveal one hint at a time.
Hint 1
x = x + 1 computes a new object (11) first, then rebinds the name x to it — it does not modify the object 10 was labeling.
Hint 2
y was labeling the same object x used to label (10). Rebinding x to a new object has no effect on any other label.
Hint 3
x is now 11 and y is still 10 — this is exactly why integers are immutable: there is no "the 10 object, but now bigger" to accidentally share.
Reveal the expected values
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
x = 10 # x labels the int object 10
y = x # y labels that same object 10
x = x + 1 # a new object 11 is created; x now labels it. y is untouched.
x -> 11
y -> 10The key difference from the first = second = [] example above: int is immutable, so
x = x + 1 can’t mutate the object 10 in place even if it wanted to — it has to build a new one.
That’s what breaks the aliasing, not anything special about x and y.
Practice: apply it
Write a small “unit converter” function, celsius_to_fahrenheit(celsius: float) -> float, and a
main() that converts three temperatures and prints each with two decimal places. Before running
it, predict whether comparing your function’s result for 0.0 against the literal 32.0 with
== is safe here.
Or reveal the answer without checking
Answer:Yes — this particular computation lands on a value floating point can represent exactly
0.0 * 9 / 5 + 32 works out to exactly 32.0 with no rounding error in this case — the 0.1 + 0.2 problem comes from specific fractions that binary floating point can't represent exactly, not from floats in general. The safe default is still to avoid relying on exact float equality, but it's worth knowing it isn't universally broken either.
Summary
- A name is a label on an object, not a box holding a value. Assignment moves labels; it doesn’t copy objects.
==compares value;iscompares identity (literally the same object, checked viaid()). They usually disagree, and==is the right default.- Chained assignment (
a = b = []) binds every target to one shared object. Multiple assignment (a, b = 1, 2) gives each target its own object from the unpacked sequence — the two look similar but behave very differently for mutable values. - Binary floating point can’t represent every decimal value exactly — never rely on
==between floats for a specific decimal result; round for display, compare with a tolerance, or usedecimal/fractionswhen exactness matters.