Mutability and references
In lesson 1.1 you learned that a name refers to a value, like a tag hanging on it, not a box holding it. With lists, that difference explains bugs that confuse even experienced programmers. By the end of this lesson you'll predict them, and make a real copy when you need one.
Two names, one list
Predict what this prints.
What will this print?
Decide before you look. Guessing wrong is how this sticks.
a = [1, 2]
b = a
b.append(3)
print(a)Python prints
[1, 2, 3]
b = a doesn't copy the list; it gives the same list a second name. Appending through b changes that one list.
If you said [1, 2], you pictured b = a making a second list. Step through what
really happens: each arrow shows which value a name refers to.
Names and objects
Press Next to run one line at a time. Each arrow shows the object a name refers to.Each row is one step: the line that just ran, and the object each name refers to after it.
a refers to a list, [1, 2].
Step 1 of 4
Step 1 of 4. Line 1: a = [1, 2]. a now refers to a new list, [1, 2].
| Step | Line | Names and objects |
|---|---|---|
| 1 | 1 |
|
| 2 | 2 |
|
| 3 | 3 |
|
| 4 | 4 |
|
Python calls each value an
.
After line 2 there is one list object with two names on it. So the rule is: assignment
never copies. b = a means "b now refers to the object a refers to". The two names
aren't tied together; they just share one object. Now a subtle one:
Quick check
After x = [1], then y = x, then y = [2], what is x?
Moving a name doesn't change the list
Press Next to run one line at a time. Each arrow shows the object a name refers to.Each row is one step: the line that just ran, and the object each name refers to after it.
x refers to a list, [1].
Step 1 of 3
Step 1 of 3. Line 1: x = [1]. x now refers to a new list, [1].
| Step | Line | Names and objects |
|---|---|---|
| 1 | 1 |
|
| 2 | 2 |
|
| 3 | 3 |
|
Changing an object and moving a name are different things. y.append(2) or y[0] = 2
would change the list, and x would see it. y = [2] only moves the name y.
The same object, or equal?
== asks "same value?". is (from lesson 2.4) asks "same object?". Here c is a
separate list with the same numbers:
a = [1, 2, 3]
b = a
c = [1, 2, 3]
print(a == c)
print(a is b)
print(a is c)In the editor, press Escape then Tab to move on.
Two equal lists are == but not is. Compare values with ==; keep is for None,
or for when you really mean "the same object".
Why numbers and strings never surprise you
Now the same shape of program with a string. Predict:
What will this print?
Decide before you look. Guessing wrong is how this sticks.
s = "ab"
t = s
t += "c"
print(s)Python prints
ab
A string can't change, so t += "c" builds a new string, "abc", and moves t to it. s still refers to the original.
Names and objects
Press Next to run one line at a time. Each arrow shows the object a name refers to.Each row is one step: the line that just ran, and the object each name refers to after it.
s refers to a str, 'ab'.
Step 1 of 4
Step 1 of 4. Line 1: s = "ab". s now refers to a new str, 'ab'.
| Step | Line | Names and objects |
|---|---|---|
| 1 | 1 |
|
| 2 | 2 |
|
| 3 | 3 |
|
| 4 | 4 |
|
Strings and numbers can't change, so sharing one never surprises you: t += "c" and
n += 1 make new objects.
A list can change. Predict this one:
What will this print?
Decide before you look. Guessing wrong is how this sticks.
a = [1, 2]
b = a
b += [3]
print(a)Python prints
[1, 2, 3]
On a list, += changes the list in place, like extend, and a refers to that same list.
+= on a list
Press Next to run one line at a time. Each arrow shows the object a name refers to.Each row is one step: the line that just ran, and the object each name refers to after it.
a refers to a list, [1, 2].
Step 1 of 4
Step 1 of 4. Line 1: a = [1, 2]. a now refers to a new list, [1, 2].
| Step | Line | Names and objects |
|---|---|---|
| 1 | 1 |
|
| 2 | 2 |
|
| 3 | 3 |
|
| 4 | 4 |
|
So b += [3] and b = b + [3] are not the same for lists: + builds a new list and
moves b to it. Run this, then change line 3 to b = b + [3] and run it again: a
stays [1, 2].
a = [1, 2]
b = a
b += [3]
print(a)In the editor, press Escape then Tab to move on.
The grid bug
You want a 3 by 3 grid of zeros, then set the top-left square to 1. Predict.
What will this print?
Decide before you look. Guessing wrong is how this sticks.
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)Python prints
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
There's only one row, in the grid three times, so changing it shows in every row.
[row] * 3 doesn't copy the row. It makes a list whose three slots all refer to the
same row:
Names and objects
Press Next to run one line at a time. Each arrow shows the object a name refers to.Each row is one step: the line that just ran, and the object each name refers to after it.
grid refers to a list, [R1, R1, R1]: all 3 slots refer to one list, [0, 0, 0].
Step 1 of 2
Step 1 of 2. Line 1: grid = [[0] * 3] * 3. grid now refers to a new list, [R1, R1, R1]: all 3 slots refer to one list, [0, 0, 0].
| Step | Line | Names and objects |
|---|---|---|
| 1 | 1 |
|
| 2 | 2 |
|
[0] * 3 repeats the number 0 the same way, but numbers can't change:
grid[0][0] = 1 moves the row's slot 0 to a new number. The shared row is what changed.
The fix is a comprehension (lesson 4.7), which runs [0] * 3 again for every row. The
name _ is a convention for "a name I won't use":
grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 1
print(grid)In the editor, press Escape then Tab to move on.
It's as if you wrote [0] * 3 three times. Step through and watch three separate
rows appear:
Three rows, three objects
Press Next to run one line at a time. Each arrow shows the object a name refers to.Each row is one step: the line that just ran, and the object each name refers to after it.
grid refers to a list, [].
Step 1 of 5
Step 1 of 5. Line 1: grid = []. grid now refers to a new list, [].
| Step | Line | Names and objects |
|---|---|---|
| 1 | 1 |
|
| 2 | 2 |
|
| 3 | 3 |
|
| 4 | 4 |
|
| 5 | 5 |
|
Making a real copy
When you do want a second list, ask for one: .copy() or list(...) both make a new
list with the same items.
a = [1, 2]
b = a.copy()
b.append(3)
print(a)
print(b)In the editor, press Escape then Tab to move on.
But what if the items are lists themselves? Predict:
What will this print?
Decide before you look. Guessing wrong is how this sticks.
data = [[1, 2], 3]
backup = data.copy()
data[0].append(9)
print(backup)Python prints
[[1, 2, 9], 3]
.copy() makes a new outer list whose slots refer to the same objects. The inner list is shared, so backup sees the 9.
.copy() copies only the outer list: a
.
For a copy all the way down, use deepcopy from Python's copy module (imported like
random in lesson 3.5):
Names and objects
Press Next to run one line at a time. Each arrow shows the object a name refers to.Each row is one step: the line that just ran, and the object each name refers to after it.
Step 1 of 5
Step 1 of 5. Line 1: import copy. Nothing in memory changed.
| Step | Line | Names and objects |
|---|---|---|
| 1 | 1 | |
| 2 | 2 |
|
| 3 | 3 |
|
| 4 | 4 |
|
| 5 | 5 |
|
backup's slot 0 refers to the same inner list as data's. safe has its own inner
list, so the 9 never reaches it. (The 3 can't change, so sharing it is harmless.) Forget
the import and Python says what's missing:
The rule
One sentence covers all of it: assignment never copies; names refer to objects, and changing an object is seen through every name that refers to it. An object that can change is : lists, dicts, and sets. Numbers, strings, and tuples are immutable. This rule comes back in Module 5, where a list used as a function's default value surprises people.
Exercise
The backup that keeps changing
This program keeps a backup of the scores before adding a new one, but the backup changes too. Fix it so the backup keeps the old scores.
Your code
scores = [67, 82, 45]
backup = scores
scores.append(90)
print(backup)
print(scores)In the editor, press Escape then Tab to move on.
Show a solutionHide the solution
This is one way to solve it, not the only one. If yours prints the same thing, it works.
scores = [67, 82, 45]
backup = scores.copy()
scores.append(90)
print(backup)
print(scores)Key takeaways
- Assignment never copies:
b = agives the same object a second name. - Changing an object (
append,+=on a list,b[0] = ...) shows through every name; moving a name (b = [9]) doesn't. ==compares values;isasks whether two names refer to the same object.[[0] * 3] * 3repeats one row; build rows with a comprehension instead..copy()is shallow: inner lists are shared.copy.deepcopy()copies all the way down.