Cloned list seems to be functioning as an alias, even though explicitly declared as a a clone

StackOverflow https://stackoverflow.com/questions/4385617

  •  10-10-2019
  •  | 
  •  

Question

I am having some trouble with the following script. It should make 3 copies of the following list so that they can be modified independently. However, it seems to be creating 3 clones of the same list, and when you modify one you modify them all. Here is the function:

def calculateProportions(strategies,proportions):
   import itertools
   combinations = []
   columns = list(itertools.product(strategies,repeat=3))
   for i in range(0,len(columns)):
      columns[i] = list(columns[i])
   for n in range(0,len(strategies)):
      combinations.append(columns[:])
   combinations[0][0][0] = "THIS SHOULD ONLY BE IN ONE PLACE"
   print combinations 
strategies = [[0,0],[0,50],[50,50]]
calculateProportions(strategies,[])

Notice how, when you run this, you see the string "THIS SHOULD BE IN ONE PLACE" 3 times (position [0][0][0],[1][0][0], and [2][0][0], not once. This appears to be because the lists are aliased together rather than cloned. However I explicitly cloned it.

I have spent the last hour banging my head into the table on this. Your suggested solutions are much appreciated!

Was it helpful?

Solution

You're only performing a shallow copy when you clone columns, i.e. the list is cloned but its items are not, so the same item references are used in both combinations and columns.

You can use the copy.deepcopy() function to perform a deep copy of the object:

def calculateProportions(strategies,proportions):
    import copy
    import itertools
    combinations = []
    columns = list(itertools.product(strategies, repeat=3))
    for i in range(0, len(columns)):
        columns[i] = list(columns[i])
    for n in range(0, len(strategies)):
        combinations.append(copy.deepcopy(columns))

Or, more simply, a list comprehension:

def calculateProportions(strategies,proportions):
    import itertools
    combinations = []
    columns = list(itertools.product(strategies, repeat=3))
    for i in range(0, len(columns)):
        columns[i] = list(columns[i])
    for n in range(0, len(strategies)):
        combinations.append([item[:] for item in columns])

OTHER TIPS

Getting a copy of a list like list[:] does not create copies of the elements contained in the list (i.e. it is a flat copy, not a deep copy). The following example code illustrates this:

>>> n1 = [1, 2]
>>> n2 = [3, 4]
>>> l1 = [n1, n2]
>>> l2 = l1[:]
>>> l2[0] = "was n1" # change in l1 only
>>> l1
[[1, 2], [3, 4]]
>>> l2
['was n1', [3, 4]]
>>> l2[1][0] = "was 3 in n2" # affects both l1 and l2
>>> l1
[[1, 2], ['was 3 in n2', 4]]
>>> l2
['was n1', ['was 3 in n2', 4]]

As suggested by ulidtko, the copy module might help in your case.

when you write

l = alist[:]

you're doing a shallow copy. that is to say that the list is different, but the two lists are pointing to the same objects. so if you modify one element of a list, the element in the other list will be modified too.

you need to make a deep copy, ie. copying the list and all the object in the list.

import copy
copy.deepcopy()
>>> import copy
>>> help(copy)

In the very first lines, you can see functions copy and deepcopy. These correspond to shallow and deep copying. For details, refer to http://en.wikipedia.org/wiki/Object_copy

Instead of trying to fix up the deep copies, I would just create the desired data with nested list comprehensions. This also avoids the ugly manual "accumulation" of the final data.

def calculateProportions(strategies, proportions):
  import itertools
  combinations = [
    [list(x) for x in itertools.product(strategies, repeat = 3)]
    for strategy in strategies
  ]
  combinations[0][0][0] = "THIS SHOULD ONLY BE IN ONE PLACE"
  print combinations 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top