1

When I define a list and try to change a single item like this:

list_of_lists = [['a', 'a', 'a'], ['a', 'a', 'a'], ['a', 'a', 'a']]
list_of_lists[1][1] = 'b'
for row in list_of_lists:
    print row

It works as intended. But when I try to use list comprehension to create the list:

row = ['a' for range in xrange(3)]
list_of_lists = [row for range in xrange(3)]
list_of_lists[1][1] = 'b'
for row in list_of_lists:
    print row

It results in an entire column of items in the list being changed. Why is this? How can I achieve the desired effect with list comprehension?

Dan Doe
  • 1,146
  • 3
  • 14
  • 25

3 Answers3

5

Think about if you do this:

>>> row = ['a' for range in xrange(3)]
>>> row2 = row
>>> row2[0] = 'b'
>>> row
['b', 'a', 'a']

This happens because row and row2 are two different names for the same list (you have row is row2) - your example with nested lists only obscures this a little.

To make them different lists, you can cause it to re-run the list-creation code each time instead of doing a variable assignment:

list_of_lists = [['a' for range in xrange(3)] for _ in xrange(3)]

or, create a new list each time by using a slice of the full old list:

list_of_lists = [row[:] for range in xrange(3)]

Although this isn't guaranteed to work in general for all sequences - it just happens that list slicing makes a new list for the slice. This doesn't happen for, eg, numpy arrays - a slice in those is a view of part of the array rather than a copy. If you need to work more generally than just lists, use the copy module:

from copy import copy
list_of_lists = [copy(row) for range in xrange(3)]

Also, note that range isn't the best name for a variable, since it shadows the builtin - for a throwaway like this, _ is reasonably common.

lvc
  • 34,233
  • 10
  • 73
  • 98
2

This happens because most objects in python (exept for strings and numbers) get passed reference (not exactly by reference, but here you have a better explanation) so when you try to do it in the "list comprehensive" way, yo get a list of 3 references to the same list (the one you called "row"). So when you change the value of one row you see that change in all of them)

So what you have to do is to change your "matrix" creation like this:

list_of_lists = [list(row) for range in xrange(3)]

Here you have some ideas on how to correctly get a copy of a list. Depending on what you are trying to do, you may use one or another...

Hope it helps!

Community
  • 1
  • 1
marianobianchi
  • 8,238
  • 1
  • 20
  • 24
  • 1
    Strings and numbers are not exceptions to the general rule - everything is a reference to an object. – Mark Ransom Sep 17 '12 at 03:42
  • yes, you are right... the difference is that they are unmutable objects so it makes me think that they are passed by copy, although i know it doesn't work that way... – marianobianchi Sep 17 '12 at 03:45
0

Copy the list instead of just the reference.

list_of_lists = [row[:] for range in xrange(3)]
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358