Tuesday 27 August 2013 — This is over 11 years old. Be careful.
In Facts and myths about Python names and values, I described how Python’s names and values work. There I posed some other questions, one of which was:
Why do beginners find it hard to make a tic-tac-toe board in Python?
Writing a game often involves a grid-like board. Python doesn’t have a native two-dimensional array, so you can use a list of lists. Making a checkerboard full of zeros can be done with a literal list:
board = [
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
]
But that’s tedious, especially if you need a 100×100 board. Even if you don’t, it seems like you could do it more compactly. Python lets you multiply lists by integers to replicate elements, so you can use this code to produce a list of eight zeros:
row = [0] * 8
Let’s try using that technique twice, once to make a row, and then again to fill out the board:
from pprint import pprint
# Construct the empty board
board = [ [0]*8 ] * 8
pprint(board)
[[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]]
It works! Now let’s change one of the cells:
# Put a 1 in the upper-left corner
board[0][0] = 1
pprint(board)
[[1, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0]]
Huh? Somehow setting one cell changed the first cell in every row!? What’s going on?
Just as with assignment, list multiplication doesn’t copy data (Fact: assignment never copies data.) When we replicate a list with multiplication, we get the same value referenced many times. When we made the single row, we made a list with eight references to the same zero:
When we make the board, the same behavior of replicating a list applies, so the board is actually eight references to the same list:
When we assign to board[0][0], we are changing one element, but that element is visible through all eight rows, because all eight rows are actually references to the same list:
In Facts and Myths, I called this the Mutable Presto-Chango.
Perhaps this is clearer if you consider that these two ways to make the board are exactly the same:
# Make it all at once
board = [ [0] * 8 ] * 8
# Make a row, then make a board
row = [0] * 8
board = [ row ] * 8
Assigning the row to its own variable doesn’t change the behavior, but when we see it assigned to a name, it’s more obvious that you’ll get eight references to the same row.
OK, but why didn’t setting one element change all 64 zeros? The diagram shows that there’s only one zero, referenced by all the elements of the board.
Two names can refer to the same value. In fact, in our case, eight names refer to the same value. The names are board[0][0], board[0][1], ..., board[0][7]. Changing one of those names to refer to 1 won’t make all of them refer to it, any more than if the eight names had been “a”, “b”, ..., “h”. (Fact: Names are reassigned independently of other names.)
The compact way to make a board that works correctly is to use a list comprehension to make each row:
board = [ [0] * 8 for i in range(8) ]
With this code, we use [0]*8 to make the row, which is fine because assignment will correctly update a single element in that row. The list comprehension makes a list with eight of those, but instead of each one being a reference to the same row, the row is recomputed each time, so a new list is made for each row.
It’s important to keep straight two different syntaxes that look very similar:
# List multiplication, doesn't work right:
board1 = [ [0] * 8 ] * 8
# List comprehension, does work right:
board2 = [ [0] * 8 for i in range(8) ]
In the first case, [0]*8 is calculated once, placed into a one-element list, and then replicated 8 times, giving 8 references to the same row. In the second case, [0]*8 is the expression in the list comprehension, and so is evaluated anew eight times, producing eight distinct lists to serve as our rows.
If this is still confusing, Facts and myths about Python names and values has more details, or try the code on pythontutor.com.
Comments
:-)
(Odd -- why does defaultdict(NoneType) not work?)
>>> import types
>>> types.NoneType()
Traceback (most recent call last):
File "", line 1, in
TypeError: cannot create 'NoneType' instances
You can use defaultdict(print) (in Py3) though. :-DD
u=[[0]]*8
v=[0]*8
or
u=[0]
v=u*8
w=[u]*8
u.append(1)
In short, you have as many lists as []'s (pairs of brackets) you evaluate. In the first case three, in the second case two of them.
also it seems more than a consequence of the rule I'm seeking than the rule itself
u=[[0]]*8
v=[0]*8
In the above, I see three []'s. So you construct three lists. First just contains 0, second contains first one eight times, and third contains 0 eight times.
Yes, it's the consequence of a general rule: evaluating literals constructs objects, variables just give them names. (As opposed to C++, for example, where declaring a variable makes an object.) As I said, read the linked post.
u=[0]
v=u*8
here I have two lists and doing
u.append(1)
only modify the first one
And another important point is, when arithmetical operations do make a new object, they make only one: their result. [u]*8 makes a new list (discarding the [u] operand, because nothing refers to it anymore), but only one, not eight of them.
One important rule of thumb is what I told you above: in all of Python's builtin namespaces, naming ("assignment") doesn't create any new objects. (Of course, you can invent your own namespace, where naming can do whatever you want.) It's the same as saying that in Python's builtin namespaces, naming doesn't print 'a' on the screen -- something that you can rely on, unless you're dealing with really strange codebases.
Add a comment: