0

This is part of a project I am working on, my question is that I don't know why are the values getting assigned to all the keys, when I just want to append them to the item with the key 'A', is there maybe another way to think it, I will need to append lists, creating a big list of lists per key.

speakers = ['A','B']
fragments = dict.fromkeys(speakers,[])
TAGS = ['A', ['121560', '124390']]
fragments[TAGS[0]].append(list(map(int,TAGS[1])))
print(fragments)
{'A': [[121560, 124390]], 'B': [[121560, 124390]]}

I need to obtain: {'A': [[121560, 124390]], 'B': []}

And if I do again: fragments[TAGS[0]].append(list(map(int,TAGS[1])))

Then I should get: {'A': [[121560, 124390],[121560, 124390]], 'B': []}

Thank you!

  • I solved it by using: ```fragments = {i:[] for i in speakers}``` Like Alexander and Tomeriko said, they were sharing memory, so the value replicated to all the keys within the dictionary. – Cristiam MJ Feb 03 '20 at 18:47

2 Answers2

3

This question is a variant of the mutable default argument question: "Least Astonishment" and the Mutable Default Argument

The problem is that you two lists for A and B are actually one and the same!

>>> id(fragments['A']) is id(fragments['B'])
True  # They both point to the same list object in memory.

One solution is to use a dictionary comprehension to instantiate your dictionary with null lists.

fragments = {k: list() for k in speakers}

Another solution would be to use defaultdict with a list as the default factory.

from collections import defaultdict

fragments = defaultdict(list)
Alexander
  • 105,104
  • 32
  • 201
  • 196
  • 1
    this really doesn't have anything to do with mutable default arguments. There is nothing in the *language* preventing `dict.fromkeys` from making a copy of that object, it simply doesn't. – juanpa.arrivillaga Feb 03 '20 at 18:59
  • 1
    Also note, `id() == id()` can give you misleading results if you don't understand, for example, consider `id([]) == id([])`. This will often return `True`, although, those are two different `list` objects being created. It is always best practice to use ` is ` – juanpa.arrivillaga Feb 03 '20 at 19:02
  • @juanpa.arrivillaga I disagree with your first point, as `fromkeys` sets the value of each key to the single argument passed to `value`, a single list object in this case. This case is similar to the mutable default argument case which also gets passed a single mutable object which gets shared by instances of the object. – Alexander Feb 03 '20 at 19:12
  • But it *doesn't involve default values*. I mean, you're right that it is the same root cause, that an object is re-used, but the same could be said about `x = []; y = []; x.append('foo')`. But if you *knew and understood* how default arguments work, that still doesn't tell you why `dict.fromkeys` works that way, because it isn't dealing with a default value. – juanpa.arrivillaga Feb 03 '20 at 19:14
  • I didn't say the issue was exactly the same, I said it was a variant. The OP was expecting separate list objects for each key but instead got the same one because of how the `fromkeys` constructor was called. The OP didn't realize that this caused the same list object to be allocated as the value to each key. – Alexander Feb 03 '20 at 19:20
1

The answer to your question is exactly the documentation of fromkeys():

fromkeys() is a class method that returns a new dictionary. value defaults to None. All of the values refer to just a single instance, so it generally doesn’t make sense for value to be a mutable object such as an empty list. To get distinct values, use a dict comprehension instead.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61