Assignment Semantics¶
The Mental Model¶
Many programmers coming from C or Java think of assignment as "putting a value
into a box." Python works differently. The statement a = 10 does not
place the integer 10 inside a container called a. Instead, Python performs
three steps:
- Evaluate the right-hand side to produce an object.
- Create or find the resulting object in memory.
- Bind the name on the left-hand side to that object.
A name in Python is a label attached to an object, not a storage location. Grasping this distinction is the single most important step toward understanding Python's runtime behavior.
How Simple Assignment Works¶
Consider the following:
python
x = 2 + 3
Python evaluates 2 + 3, producing the integer object 5. It then binds the
name x to that object. The name x now refers to (points at) the object
5.
python
x = 2 + 3
y = x
print(x is y) # True -- both names point to the same object
After y = x, the names x and y are two labels attached to the same
object. No copy is made. You can verify this with id():
python
x = [1, 2, 3]
y = x
print(id(x)) # e.g. 140234866203200
print(id(y)) # same number
Chained Assignment¶
Python allows chaining assignments:
python
a = b = c = 0
This evaluates the right-hand side once, producing a single int object 0,
then binds the names a, b, and c to that same object. It is equivalent
to:
python
temp = 0
a = temp
b = temp
c = temp
All three names share identity:
python
a = b = c = []
print(a is b) # True
print(b is c) # True
a.append(1)
print(c) # [1] -- same object
Chained assignment with mutable objects
Because chained assignment creates one object shared by multiple names,
mutating through any name affects all of them. If you need independent
lists, create separate literals: a, b, c = [], [], [].
Simultaneous (Tuple-Unpacking) Assignment¶
Simultaneous assignment evaluates all right-hand side expressions before any binding occurs:
python
a, b = 1, 2
Python builds a tuple (1, 2), then unpacks it, binding a to 1 and b
to 2.
The classic swap idiom relies on this guarantee:
python
x = "left"
y = "right"
x, y = y, x
print(x) # right
print(y) # left
No temporary variable is needed because the right-hand tuple (y, x) is fully
evaluated before the names x and y are rebound.
Simultaneous assignment also supports nested unpacking:
python
(a, b), c = (1, 2), 3
print(a, b, c) # 1 2 3
Augmented Assignment¶
Augmented assignment operators (+=, -=, *=, etc.) combine an operation
with rebinding:
python
count = 10
count += 5 # equivalent to count = count + 5
print(count) # 15
Immutable objects¶
For immutable types like int, float, and str, augmented assignment always
creates a new object:
python
n = 100
original_id = id(n)
n += 1
print(id(n) == original_id) # False -- n now refers to a new int object
Mutable objects¶
For mutable types like list, augmented assignment may modify the object
in place by calling the __iadd__ method:
python
items = [1, 2, 3]
original_id = id(items)
items += [4, 5]
print(id(items) == original_id) # True -- same list, modified in place
This matters when multiple names refer to the same object:
python
a = [1, 2]
b = a
a += [3]
print(b) # [1, 2, 3] -- b sees the mutation because a and b share the object
Compare with plain +, which always creates a new object:
python
a = [1, 2]
b = a
a = a + [3]
print(b) # [1, 2] -- b still refers to the original list
The += asymmetry
a += b and a = a + b are not always equivalent. For mutable types,
+= mutates in place (and rebinds), while a = a + b creates a new object
and rebinds. For immutable types, both create a new object.
The tuple-with-list surprise¶
One of Python's most surprising behaviors involves += on a mutable element inside an immutable container:
```python t = ([1, 2],)
try: t[0] += [3] except TypeError as e: print("Error:", e)
print(t) # ([1, 2, 3],) ```
This both raises an error and mutates the list. Why? Because += is a two-step operation:
t[0].__iadd__([3])--- mutates the list in place (succeeds)t[0] = result--- tries to assign back into the tuple (fails)
Step 1 already changed the list before step 2 fails. This demonstrates that += is not an atomic operation.
The Right-Hand Side Is Always Evaluated First¶
This rule applies to every form of assignment. The right-hand side is fully evaluated to produce an object (or tuple of objects) before any name binding happens on the left:
python
x = 5
x = x * 2 + 1 # RHS evaluates to 11 using the current value of x, then x is rebound
print(x) # 11
This principle guarantees that simultaneous swap works, that augmented assignment reads the current value before writing, and that chained assignment evaluates once.
Visualizing Assignment with id()¶
The built-in id() function returns an object's unique identity (its memory
address in CPython). Use it to trace what assignment does:
```python a = "hello" print(f"a -> id {id(a)}")
b = a print(f"b -> id {id(b)}") # same as a
a = "world" print(f"a -> id {id(a)}") # different -- a now points to a new object print(f"b -> id {id(b)}") # unchanged -- b still points to "hello" ```
Example output:
a -> id 140539436498736
b -> id 140539436498736
a -> id 140539436498800
b -> id 140539436498736
Rebinding a does not affect b. The old object "hello" still exists as
long as b refers to it.
Summary¶
| Concept | What happens |
|---|---|
Simple assignment x = expr |
Evaluate expr, bind x to the resulting object |
Chained assignment a = b = expr |
Evaluate expr once, bind both names to the same object |
Simultaneous assignment a, b = x, y |
Evaluate all RHS expressions, then bind all LHS names |
Augmented assignment x += expr |
Evaluate expr, call __iadd__ (mutable) or __add__ (immutable), rebind x |
The unifying principle: assignment binds names to objects; it never copies values into containers.
Exercises¶
Exercise 1. Predict the output of the following code. Explain what happens at each assignment step in terms of object creation and name binding.
python
a = [10, 20]
b = a
c = a
a = a + [30]
print(a)
print(b)
print(a is b)
print(b is c)
Solution to Exercise 1
Output:
text
[10, 20, 30]
[10, 20]
False
True
Step-by-step:
a = [10, 20]creates a list object and bindsato it.b = abindsbto the same list object.c = abindscto the same object as well. At this pointa,b, andcall share identity.a = a + [30]evaluates the right-hand side:a + [30]concatenates the existing list with[30], producing a new list[10, 20, 30]. The nameais then rebound to this new list. The namesbandcstill refer to the original list[10, 20].a is bisFalsebecauseanow points to the new list.b is cisTruebecause both still point to the original list.
Exercise 2.
A colleague claims that x += [4] and x = x + [4] are interchangeable when
x is a list. Write a short program that demonstrates they are not
equivalent. Your program should use a second name that shares the same list
object and print results showing the difference.
Solution to Exercise 2
```python
Demonstrating that += and + differ for lists¶
Case 1: augmented assignment (+=)¶
a = [1, 2, 3] b = a a += [4] print("After a += [4]:") print(f" a = {a}") # [1, 2, 3, 4] print(f" b = {b}") # [1, 2, 3, 4] print(f" a is b: {a is b}") # True
Case 2: concatenation assignment (= ... +)¶
a = [1, 2, 3] b = a a = a + [4] print("After a = a + [4]:") print(f" a = {a}") # [1, 2, 3, 4] print(f" b = {b}") # [1, 2, 3] print(f" a is b: {a is b}") # False ```
In Case 1, += calls list.__iadd__, which extends the list in place and
rebinds a to the same object. Since b shares that object, b sees the
change. In Case 2, a + [4] creates a brand-new list. The name a is
rebound to the new list, leaving b still pointing at the original
unchanged list.
Exercise 3.
Using simultaneous assignment, write a single line of code that rotates three
variables so that the value in a moves to b, the value in b moves to
c, and the value in c moves to a. Start with a, b, c = 1, 2, 3 and
print the result. Explain why this works without a temporary variable.
Solution to Exercise 3
python
a, b, c = 1, 2, 3
a, b, c = c, a, b
print(a, b, c) # 3 1 2
The right-hand side c, a, b is fully evaluated before any name
binding occurs on the left. Python builds the tuple (3, 1, 2) using the
current values of c, a, and b. Only then does it unpack this tuple,
binding a to 3, b to 1, and c to 2. Because evaluation and
binding are separate phases, no temporary variable is needed and no value
is lost during the rotation.