Skip to content

ይህ ትምህርት ገና ወደ አማርኛ አልተተረጎመም፤ ስለዚህ በእንግሊዝኛ ቀርቧል። የእንግሊዝኛውን ገጽ ክፈቱ

Mutability and references

4 min read

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.

ውጤቱን ገምቱ

Decide before you look. Guessing wrong is how this sticks.

a = [1, 2]
b = a
b.append(3)
print(a)
Pick the output

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.

a = [1, 2]
b = a
b.append(3)
print(a)

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

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:

ጥያቄ

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.

x = [1]
y = x
y = [2]

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

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:

ውጤቱን ገምቱ

Decide before you look. Guessing wrong is how this sticks.

s = "ab"
t = s
t += "c"
print(s)
Pick the output

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.

s = "ab"
t = s
t += "c"
print(s)

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

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:

ውጤቱን ገምቱ

Decide before you look. Guessing wrong is how this sticks.

a = [1, 2]
b = a
b += [3]
print(a)
Pick the output

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.

a = [1, 2]
b = a
b += [3]
print(a)

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

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.

ውጤቱን ገምቱ

Decide before you look. Guessing wrong is how this sticks.

grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)
Pick the output

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.

grid = [[0] * 3] * 3
grid[0][0] = 1

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

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

grid = []
grid.append([0] * 3)
grid.append([0] * 3)
grid.append([0] * 3)
grid[0][0] = 1

grid refers to a list, [].

Step 1 of 5

Step 1 of 5. Line 1: grid = []. grid now refers to a new list, [].

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:

ውጤቱን ገምቱ

Decide before you look. Guessing wrong is how this sticks.

data = [[1, 2], 3]
backup = data.copy()
data[0].append(9)
print(backup)
Pick the output

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.

import copy
data = [[1, 2], 3]
backup = data.copy()
safe = copy.deepcopy(data)
data[0].append(9)

Step 1 of 5

Step 1 of 5. Line 1: import copy. Nothing in memory changed.

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.

መልመጃ

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.

መፍትሄውን አሳይ

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)

ዋና ዋና ነጥቦች

  • Assignment never copies: b = a gives 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; is asks whether two names refer to the same object.
  • [[0] * 3] * 3 repeats one row; build rows with a comprehension instead.
  • .copy() is shallow: inner lists are shared. copy.deepcopy() copies all the way down.