0

I've read that python treats all variable assignments as references instead of copy. So the code below for generating 3 independent lists wouldn't work:

sizeNeeded = 4
itemDateNums = itemWeights = itemVolumes = [[]] * sizeNeeded

itemDateNums[1].append("hello world")
# All instances are now hello world because of referencing

So I rewrote the code:

sizeNeeded = 4
itemDateNums = []
itemWeights = []
itemVolumes = []

for shifts in range(sizeNeeded):
    itemDateNums.append([])
    itemWeights.append([])
    itemVolumes.append([])

itemDateNums[1].append("hello world")

But the syntax looks very redundant. Is there an cleaner way of expressing that a copy assign is needed instead of reference assign?

Si Te Feng
  • 135
  • 1
  • 14
  • 4
    I'm voting to close as unclear because I don't understand your background assumptions. The "clean way" to create a "dynamically sized array" in Python is: `[]`. Lists themselves are already dynamically sized arrays. Likewise, nothing in Python needs to be initialized with blank spaces to some pre-defined size. – Two-Bit Alchemist Dec 17 '15 at 16:48
  • The goal is to create an array of arrays [[3,4,5],[5,6,7]] without using a for loop. Although python lists are dynamic arrays, creating it with the shorthand syntax will not work, and you do need a pre-defined size for array of array. Any shorter way of expressing the same? – Si Te Feng Dec 17 '15 at 17:05
  • len(sizeNeeded) doesn't look ok to me... – dyeray Dec 17 '15 at 17:11
  • oops, fixed the code – Si Te Feng Dec 17 '15 at 17:14
  • Do you want jagged arrays, or grids? Your example code seems to want jagged arrays, but your comments seem to indicate you want a grid. If you're doing matrix work, you'll definitely want to ditch all this and go get [NumPy](http://www.numpy.org/). If you're not doing numerical computation, sticking with lists of lists is probably fine. – user2357112 Dec 17 '15 at 17:29
  • "you do need a pre-defined size for array of array" -- nope, that is false. – Two-Bit Alchemist Dec 17 '15 at 18:10

3 Answers3

1

Nothing needs to be pre-initialized with a size, as Two-Bit Alchemist said above, but it is often desireable for efficiency, if you know what size your array is going to be.

Check out the generic copy module

>>from copy import copy as cp
>>a = [[] for _ in range(4)]
>>a
a = [[],[],[],[]]
>>b = cp(a)
>>a[1] = 4
>>a
a = [[],4,[],[]]
b = [[],[],[],[]]

But then you would still need three lines:

itemDateNums = [[] for _ in range(sizeNeeded)]
itemWeights = cp(itemDateNums)
itemVolumes = cp(itemVolumes)

Or two if you really want, but its not so pretty:

itemDateNums = [[] for _ in range(sizeNeeded)]
itemWeights, itemVolumes = cp(itemDateNums), cp(itemDateNums)

I'm not sure there is a much cleaner way to achieve this using the standard lib, without installing NumPy or similar. But I will update if I find out.

Edit: Also have a look at copy.deepcopy: What exactly is the difference between shallow copy, deepcopy and normal assignment operation?

Also, as tobias_k 5 mentions below, a = [[]]*4 is not safe:

>>a = [[]]*4
>>b = [[] for _ in range(4)]
>>a[0].append(5)
>>b[0].append(5)
>>print ('a:{}\nb:{}'.format(a,b))
a: [[5], [5], [5] ,[5]]
b: [[5], [], [], []]
Community
  • 1
  • 1
pretzlstyle
  • 2,774
  • 5
  • 23
  • 40
  • 1
    `[[]]*sizeNeeded` is a really bad idea. This will be a list with `sizeNeeded` _references_ to _the same_ empty list! Do `[[] for _ in range(sizeNeeded)]` instead! – tobias_k Dec 17 '15 at 17:11
  • This is true, I will update – pretzlstyle Dec 17 '15 at 17:15
  • sorry, the copy function didn't solve the reference problem. If you change the code from `a[1]=4` to `a[1].append(4)` and `b[1].append(5)`, both a and b will have `[[],[4,5],[],[]]` – Si Te Feng Dec 18 '15 at 18:21
  • Instead of `copy.copy` you should rather use `copy.deepcopy`. You first example works only because you _replace_ an element from `a` instead of appending to it. – tobias_k Dec 18 '15 at 19:57
1

Note that there are two problems with your code:

  • As you noted, x = y = z = [some list] will assign the same object to each varaible. In the case of immutable objects, like numbers or strings, this is okay, but not for lists.
  • Also, [[]] * number does not do what you'd expect, since the resulting list will have number references to the same empty list, i.e. when you append to one, you append to all of them!

Instead, you can try the following, using list comprehensions for creating the (doubly-nested) list and tuple-unpacking to "distribute" it to the variables:

>>> itemDateNums, itemWeights, itemVolumes = [[[] for _ in range(sizeNeeded)] for _ in range(3)]
>>> itemDateNums[1].append("hello world")
>>> itemDateNums
[[], ['hello world'], [], []]
>>> itemWeights
[[], [], [], []]

Of course, whether that's cleaner is another question. IMHO, you should just split it up onto three lines. The code duplication is not all that bad, and that way it's immediately clear what the code does.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
0

[[]] * sizeNeeded has the problem of making multiple references to the same single inner element (regardless of how many references to the same single outer list you make). List comprehensions don't have the same problem:

itemDateNums = [ [] for i in range( sizeNeeded ) ]
itemWeights  = [ [] for i in range( sizeNeeded ) ]
itemVolumes  = [ [] for i in range( sizeNeeded ) ]

Or, at the risk of getting a little obfuscated, you can do it in one assignment with:

itemDateNums, itemWeights, itemVolumes = zip( *[ ([],[],[]) for i in range( sizeNeeded )  ] )
jez
  • 14,867
  • 5
  • 37
  • 64