unexpected behavior when using append method on python dictionary [duplicate]

This was causing me a bit of grief...

I created a dictionary from a list

l = ['a','b','c']
d = dict.fromkeys(l, [0,0]) # initializing dictionary with [0,0] as values

d['a'] is d['b'] # returns True

How can I make each value of the dictionary a seperate list? Is this possible without iterating over all keys and setting them equal to a list? I'd like to modify one list without changing all the others.


Solution 1:

You could use a dict comprehension:

>>> keys = ['a','b','c']
>>> value = [0, 0]
>>> {key: list(value) for key in keys}
    {'a': [0, 0], 'b': [0, 0], 'c': [0, 0]}

Solution 2:

This answer is here to explain this behavior to anyone flummoxed by the results they get of trying to instantiate a dict with fromkeys() with a mutable default value in that dict.

Consider:

#Python 3.4.3 (default, Nov 17 2016, 01:08:31) 

# start by validating that different variables pointing to an
# empty mutable are indeed different references.
>>> l1 = []
>>> l2 = []
>>> id(l1)
140150323815176
>>> id(l2)
140150324024968

so any change to l1 will not affect l2 and vice versa. this would be true for any mutable so far, including a dict.

# create a new dict from an iterable of keys
>>> dict1 = dict.fromkeys(['a', 'b', 'c'], [])
>>> dict1
{'c': [], 'b': [], 'a': []}

this can be a handy function. here we are assigning to each key a default value which also happens to be an empty list.

# the dict has its own id.
>>> id(dict1)
140150327601160

# but look at the ids of the values.
>>> id(dict1['a'])
140150323816328
>>> id(dict1['b'])
140150323816328
>>> id(dict1['c'])
140150323816328

Indeed they are all using the same ref! A change to one is a change to all, since they are in fact the same object!

>>> dict1['a'].append('apples')
>>> dict1
{'c': ['apples'], 'b': ['apples'], 'a': ['apples']}
>>> id(dict1['a'])
>>> 140150323816328
>>> id(dict1['b'])
140150323816328
>>> id(dict1['c'])
140150323816328

for many, this was not what was intended!

Now let's try it with making an explicit copy of the list being used as a the default value.

>>> empty_list = []
>>> id(empty_list)
140150324169864

and now create a dict with a copy of empty_list.

>>> dict2 = dict.fromkeys(['a', 'b', 'c'], empty_list[:])
>>> id(dict2)
140150323831432
>>> id(dict2['a'])
140150327184328
>>> id(dict2['b'])
140150327184328
>>> id(dict2['c'])
140150327184328
>>> dict2['a'].append('apples')
>>> dict2
{'c': ['apples'], 'b': ['apples'], 'a': ['apples']}

Still no joy! I hear someone shout, it's because I used an empty list!

>>> not_empty_list = [0]
>>> dict3 = dict.fromkeys(['a', 'b', 'c'], not_empty_list[:])
>>> dict3
{'c': [0], 'b': [0], 'a': [0]}
>>> dict3['a'].append('apples')
>>> dict3
{'c': [0, 'apples'], 'b': [0, 'apples'], 'a': [0, 'apples']}

The default behavior of fromkeys() is to assign None to the value.

>>> dict4 = dict.fromkeys(['a', 'b', 'c'])
>>> dict4
{'c': None, 'b': None, 'a': None}
>>> id(dict4['a'])
9901984
>>> id(dict4['b'])
9901984
>>> id(dict4['c'])
9901984

Indeed, all of the values are the same (and the only!) None. Now, let's iterate, in one of a myriad number of ways, through the dict and change the value.

>>> for k, _ in dict4.items():
...    dict4[k] = []

>>> dict4
{'c': [], 'b': [], 'a': []}

Hmm. Looks the same as before!

>>> id(dict4['a'])
140150318876488
>>> id(dict4['b'])
140150324122824
>>> id(dict4['c'])
140150294277576
>>> dict4['a'].append('apples')
>>> dict4
>>> {'c': [], 'b': [], 'a': ['apples']}

But they are indeed different []s, which was in this case the intended result.

Solution 3:

You can use this:

l = ['a', 'b', 'c']
d = dict((k, [0, 0]) for k in l)

Solution 4:

l = ['a','b','c']
d = dict((i, [0, 0]) for i in l)