PYTHON / OPERATORS
Assignment and augmented assignment
Bind names with =, unpack and swap with tuple targets, and predict when += mutates an object in place versus rebinding the name.
What you will learn
- Read x = y as binding a name to an existing object, not copying its value
- += mutates lists and dicts in place but rebinds the name for ints, strings, tuples
- Use a, b = b, a to swap, knowing the right-hand tuple is built before unpacking
- Spot aliasing bugs where += through one name changes what another name shows
Understanding Assignment and augmented assignment
In Python, = is a statement that attaches a name to an object. The right side is evaluated completely first, then the result is bound to the target on the left, where a target can be a plain name, a subscription like d[k], an attribute like obj.x, or a tuple of targets. Because the right side finishes before any binding happens, a, b = b, a swaps two names correctly: the pair (b, a) is built as a tuple, then unpacked. Chained targets share one object: x = y = [] evaluates the list literal once and gives both names the same empty list.
Augmented assignment is not merely shorthand for the long form. x += y first tries x = x.__iadd__(y); only if the type has no __iadd__ does Python fall back to x = x + y. list defines __iadd__, which extends the existing list and returns the same object, so identity is preserved and every alias sees the new items. int, str and tuple are immutable and define no in-place hook, so n += 1 quietly builds a new object and points the name at it.
The mental model is that names are labels stuck on objects. On a mutable object, += edits the object under the label; on an immutable one, it moves the label to a different object. That also explains two rules that surprise beginners: the target of += must already be bound, because Python has to read the old value before writing back, and a subscript target only works if the container supports item assignment, which is why d[k] += 1 is fine for a dict but fails for a tuple.
counts = [1, 2, 3]
alias = counts
counts += [4] # list.__iadd__ extends the same object
print(counts, alias, counts is alias)
counts = counts + [5] # builds a new list, rebinds only 'counts'
print(counts, alias, counts is alias)
n = 10
before = id(n)
n += 1 # ints are immutable, so n is rebound
print(n, id(n) == before)
a, b = 1, 2
a, b = b, a # right side is evaluated into a tuple first
print(a, b)Assignment binds a name to an object, and augmented assignment asks the object to update itself in place, rebinding the name only when the object cannot.
Worked examples
Chained targets share one object
Shows that x = y = expr evaluates the expression once and binds both names to the same object.
row1 = row2 = [0, 0]
row1[0] = 9
print(row1, row2)
grid = [[0, 0] for _ in range(2)]
grid[0][0] = 9
print(grid)Example explained
Line 1row1 = row2 = [0, 0] creates exactly one list, so both names label the same object.
Line 2row1[0] = 9 is an item assignment on that shared list, so row2 shows the change too.
Line 3The comprehension runs [0, 0] once per iteration, producing two independent lists.
Line 4grid[0][0] = 9 therefore touches only the first inner list.
+= on a tuple item mutates, then fails
Demonstrates that augmented assignment is a read, an in-place update, and a store back into the target.
t = ([1], "x")
try:
t[0] += [2]
except TypeError as e:
print("TypeError:", e)
print(t)Example explained
Line 1t[0] is read first, and list.__iadd__([2]) extends that inner list in place.
Line 2Python then tries to store the result back into t[0], which tuples forbid, raising TypeError.
Line 3The final print shows [1, 2]: the mutation already happened before the store failed.
Line 4So a failed augmented assignment can still leave your data changed.
Augmented forms of other operators
Applies +=, *=, //= and **= to a dict item, a string and an int.
totals = {"a": 1}
totals["a"] += 5
print(totals)
s = "ab"
s *= 3
print(s)
x = 7
x //= 2
x **= 2
print(x)Example explained
Line 1totals['a'] += 5 reads the key, adds 5, and stores back, which dicts allow.
Line 2str has no in-place hook, so s *= 3 rebinds s to a new six-character string.
Line 3x //= 2 floors 7 to 3, then x **= 2 squares it to 9.
Line 4Every arithmetic and bitwise operator has a matching augmented form.
Important notes
x = (y = 3) is a SyntaxError because assignment produces no value; := is the separate expression form and is only legal in places like conditions and comprehensions.
Even when += mutates in place, it is still a read followed by a store, so it is not atomic across threads and it fails on read-only targets such as tuple items.
Common mistakes
Using count += 1 before count has any value: Python must read the old value first, so you get NameError (or UnboundLocalError inside a function), not an implicit zero.
Writing backup = data to snapshot a list and then doing data += [...]: += mutates the shared list, so backup changes with it and the snapshot is worthless.
Typing if x = 5: instead of if x == 5:: assignment is a statement, not an expression, so this is a SyntaxError that Python reports as maybe meaning '==' or ':='.
Try it yourself
Change, predict, then run
Create scores = [10, 20] and saved = scores, then run scores += [30] and afterwards scores = scores + [40], printing scores, saved and scores is saved after each step. Note which of the two statements broke the alias and why.
Open the Python workspaceCheck your understanding
After a = [1, 2]; b = a; a += [3]; a = a + [4], what does b contain?
- [1, 2]
- [1, 2, 3]
- [1, 2, 3, 4]
- [1, 2, 4]
Show answer
a += [3] calls list.__iadd__, which extends the one list both names point to, so b sees the 3. The next line evaluates a + [4] into a brand new list and rebinds only a, leaving b at [1, 2, 3]; [1, 2, 3, 4] would be right only if both statements mutated in place.