BeginnerNames, values, and assignment~30 minPython

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:

PythonTwo names, one object
first = []
second = first
first.append("milk")

print(second)
Output
['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.

Pythonnames_values_and_assignment.py
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()
Output
Subtotal: $28.74
Total with tax: $31.04
a == b: True
a is b: False

a 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

PythonA classic surprise
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
Output
0.30000000000000004
False

Check your understanding

What does this print? first = second = [] first.append("a") second.append("b") print(first)
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.

x = 5.0 and y = 5.0 are two separately-written float literals. What does x is y most reliably tell you?
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.

  1. 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.

  2. 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.

  3. 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  -> 10

The 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.

celsius_to_fahrenheit(0.0) computes 0.0 * 9 / 5 + 32. Is comparing that result to 32.0 with == safe?
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; is compares identity (literally the same object, checked via id()). 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 use decimal/fractions when exactness matters.

Related reference