[Tutor] Making Doubly Linked List with Less Lines of Code.

Steven D'Aprano steve at pearwood.info
Thu Jan 1 01:18:44 CET 2015


On Tue, Dec 30, 2014 at 09:41:33PM -0800, Alex Kleider wrote:

> In the process of doing so I discovered that list multiplication does 
> not at all behave the way I expected (as demonstrated by the 
> 'bad_flip_2_D' function.)

Well, don't keep us in suspense. How did you expect it to behave, and 
how does it actually behave?

My guess is that you expected this:

py> grid = [[0]*5]*4   # make a 4x5 grid
py> print(grid)
[[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

Looks good so far! But:

py> grid[1][1] = 99  # Adjust a single cell.
py> print(grid)
[[0, 99, 0, 0, 0], [0, 99, 0, 0, 0], [0, 99, 0, 0, 0], [0, 99, 0, 0, 0]]

while you expected:

[[0, 0, 0, 0, 0], [0, 99, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]


Am I right?


The problem is that you guessed that list multiplication *copies* the 
items. It does not. It just repeats them. So:

    [a, b]*3

returns 

    [a, b, a, b, a, b]

but NOT 

    [copy(a), copy(b), copy(a), copy(b), copy(a), copy(b)]

If the list items are immutable, like ints or strings, the difference 
doesn't matter. You can't modify immutable objects in-place, so you 
never notice any difference between copying them or not.

Aside: the copy module actually implements copying for some immutable 
objects by returning the original!

py> import copy
py> x = 2.3456
py> y = copy.copy(x)
py> y is x
True
py> x = []
py> y = copy.copy(x)
py> y is x
False

Floats are immutable and cannot be modified, so there is nothing you can 
do to change float x. Making an actual copy is a waste of time, so 
copy.copy just returns the same float object. But lists are mutable and 
can be modified, so copy.copy has to actually build a new list.

But I digress. Back to list multiplication.

Since list multiplication doesn't *copy* the items, it just repeats 
them, we can see what is happening if we expand the code a little with a 
temporary variable:

Before:

grid = [[0]*5]*4   # make a 4x5 grid
grid[1][1] = 99  # Adjust a single cell.


Expanded:

x = [0]*5  # Make a single list of five immutable integers.
grid = [x]*4  # Like [x, x, x, x]

So here we have a list of four references to the same list, not four 
different lists! If you change any one of those references, or indeed 
the original "x" reference *in-place*, they all change. They all change 
because in fact there is no "all", there is only one!

grid[1][1] = 99
print(x)
-> prints [0, 99, 0, 0, 0]


The solution to this is not to use list multiplication unless you know 
the items are immutable. Instead, a list comprehension solves the 
problem nicely:

py> grid = [[0]*5 for i in range(4)]
py> grid[1][1] = 99
py> print(grid)
[[0, 0, 0, 0, 0], [0, 99, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]



-- 
Steven


More information about the Tutor mailing list