2

I have read these posts here explaining exactly the problem I am having, but none of the solutions are working:

Changing one list unexpectedly changes another, too

Changing One dict value changes all values

I have a specific shape of 2D array I need to initialize the entries in a dictionary with. Here is how I have done it:

empty = []
for x in range(2):
    empty.append([])
    for y in range(2):
        empty[x].append(False)

status = {k:[] for k in ["a", "b", "c"]}

status["a"] = list(empty)
status["b"] = list(empty)
status["c"] = list(empty)

print(status)
status["a"][0][0] = True
print(status)

(Shape of list simplified for example)

This prints:

{'a': [[False, False], [False, False]], 'b': [[False, False], [False, False]], 'c': [[False, False], [False, False]]}
{'a': [[True, False], [False, False]], 'b': [[True, False], [False, False]], 'c': [[True, False], [False, False]]}

As you can see, setting one of the lists values changes all of the lists. I do not want this, I want them to be separate lists (in one dictionary) with different values.

Initially, I thought I had done the old newlist = oldlist blunder where I set newlist to the same object as oldlist, but nope. As you can see in my code, I am making separate lists using newlist = list(oldlist). I have also tried newlist = oldlist[:], newlist = oldlist.copy(), etc.

What am I missing?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Ella Jameson
  • 408
  • 3
  • 8
  • 5
    I guess you've seen https://stackoverflow.com/questions/240178/list-of-lists-changes-reflected-across-sublists-unexpectedly ?`list(empty)` _does_ create a new list, but the inner lists of that new list are just references to the same list objects in `empty`. – PM 2Ring Oct 04 '18 at 08:48
  • 2
    Mandatory reading: https://nedbatchelder.com/text/names.html – bruno desthuilliers Oct 04 '18 at 08:51

4 Answers4

5
from copy import deepcopy
empty = []
for x in range(2):
    empty.append([])
    for y in range(2):
        empty[x].append(False)

status = {k:[] for k in ["a", "b", "c"]}

status["a"] = deepcopy(empty)
status["b"] = deepcopy(empty)
status["c"] = deepcopy(empty)

print(status)
status["a"][0][0] = True
print(status)

you were close with the copy, but you actually neeed a deepcopy

redacted
  • 3,789
  • 6
  • 25
  • 38
3

As I said earlier, list(empty) does create a new list, but the inner lists of that new list are just references to the same list objects in empty.

You can use deepcopy, but I think it's simpler just to build new lists when you need them. The deepcopy function jumps through various hoops that are required to copy arbitrarily deep nested objects that can contain anything. But why bother going through all that stuff when you have a structure that's simple to just build from scratch?

Using a list comprehension, your code can be written like this:

status["a"] = [[False] * 2 for _ in range(2)]
status["b"] = [[False] * 2 for _ in range(2)]
#etc

It's safe to do [False] * 2 for the innermost lists: it's always safe to share immutable objects (booleans, integers, strings, etc). The problem only arises when you unexpectedly share a mutatable object and you mutate its value instead of replacing it with a new object.

To avoid re-writing that list comp, you can use a function to build the lists for you. Eg,

def empty(rows, columns):
    return [[False] * columns for _ in range(rows)]

status = {key: empty(2, 2) for key in 'abc'}
print(status)
status["a"][0][0] = True
print(status)

output

{'a': [[False, False], [False, False]], 'b': [[False, False], [False, False]], 'c': [[False, False], [False, False]]}
{'a': [[True, False], [False, False]], 'b': [[False, False], [False, False]], 'c': [[False, False], [False, False]]}
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
2

You can see that the first element of your empty list is shared across all the values in your dictionary:

>>> id(empty[0]) == id(status['a'][0]) == id(status['b'][0]) == id(status['c'][0])
True

They each share the same memory location. The same is also true for the second item in the empty list, id(empty[1]) == id(status['a'][1]) == ...

The reason for this is your assignment of your empty list to each value. You can either perform a deep copy this empty, nested list, or else use a list comprehension to generate new ones for each key in your dictionary. This list comprehension for the empty list creation is itself done within a dictionary comprehension to generate your status variable for all the required keys.

empty_rows = 2
empty_cols = 2
keys = ['a', 'b', 'c']
status = {k: [[False] * empty_cols 
              for _ in range(empty_rows)] 
          for k in keys}

# Example usage:
status['a'][0][0] = True
>>> status
{'a': [[True, False], [False, False]],
 'b': [[False, False], [False, False]],
 'c': [[False, False], [False, False]]}

Now you can change any element without it affecting the others.

Alexander
  • 105,104
  • 32
  • 201
  • 196
  • +1 for the `id()` function. I would recommend to the OP to really play around with it and understand what's going on – redacted Oct 04 '18 at 10:46
0

I think what you need is a deep copy, see documentation of copy.deepcopy()

import copy
empty = []
for x in range(2):
    empty.append([])
    for y in range(2):
        empty[x].append(False)

status = {k:[] for k in ["a", "b", "c"]}

status["a"] = copy.deepcopy(empty)
status["b"] = copy.deepcopy(empty) 
status["c"] = copy.deepcopy(empty)
status['a'][0][0] = True
print(status)
braulio
  • 543
  • 2
  • 13
  • hey, it is not different, I was typing the answer, got busy with other things, and when submitted the previous answers were already there ... (perhaps, the only thing is that I had added a reference to the python docs) – braulio Oct 04 '18 at 09:56