Pourquoi dict.get (key) fonctionnait-il mais pas dict [key]?

17

J'essaie de regrouper les chaînes binaires de certains nombres en fonction du nombre de 1 dans la chaîne.

Cela ne fonctionne pas:

s = "0 1 3 7 8 9 11 15"
numbers = map(int, s.split())
binaries = [bin(x)[2:].rjust(4, '0') for x in numbers]

one_groups = dict.fromkeys(range(5), [])
for x in binaries:
    one_groups[x.count('1')] += [x]

Le dictionnaire attendu one_groupsdoit être

{0: ['0000'], 
 1: ['0001', '1000'], 
 2: ['0011', '1001'], 
 3: ['0111', '1011'], 
 4: ['1111']}

Mais je reçois

{0: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 1: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 2: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 3: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111'], 
 4: ['0000', '0001', '0011', '0111', '1000', '1001', '1011', '1111']}

Jusqu'à présent, la seule chose qui a fonctionné est si j'utilise one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]au lieu deone_groups[x.count('1')] += [x]

Mais pourquoi en est-il ainsi? Si je me souviens bien, n'est-il pas dict[key]censé renvoyer la valeur de ce dictionnaire, comme cela dict.get(key)fonctionne? J'ai vu ce fil Pourquoi dict.get (key) au lieu de dict [key]? mais il n'a pas répondu à ma question pour ce cas particulier, car je sais avec certitude que le programme n'est pas destiné à obtenir leKeyError

J'ai également essayé one_groups[x.count('1')].append(x)mais cela ne fonctionne pas non plus.

SpectraXCD
la source
8
getrenvoie Nonesi la clé n'existe pas ou toute valeur par défaut fournie, tandis que l'opérateur d'index []génère une erreur si la clé n'existe pas.
adnanmuttaleb
Sidenote, bin(x)[2:].rjust(4, '0')peut être simplifié '{:0>4b}'.format(x).
wjandrea
1
BTW cela aide à faire un exemple reproductible minimal . Dans ce cas, la façon dont vous créez binariesn'est pas pertinente pour la question, vous pouvez donc simplement fournir sa valeur.
wjandrea
1
Est-ce que cela répond à votre question? dict.fromkeys pointe tous vers la même liste
Georgy

Réponses:

24

Le problème est la mutabilité:

one_groups = dict.fromkeys(range(5), [])- cela transmet la même liste que la valeur à toutes les clés . Donc, si vous changez une valeur, vous les changez tous.

C'est essentiellement la même chose que de dire:

tmp = []
one_groups = dict.fromkeys(range(5), tmp)
del tmp

Si vous souhaitez utiliser une nouvelle liste, vous devez le faire en boucle - soit une forboucle explicite soit une compréhension de dict:

one_groups = {key: [] for key in range(5)}

Cette chose "s'exécutera" [](ce qui équivaut à list()) pour chaque clé, créant ainsi les valeurs avec des listes différentes.


Pourquoi ça getmarche? Parce que vous prenez explicitement la liste actuelle, mais +créez une nouvelle liste de résultats. Et peu importe que ce soit one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]ou one_groups[x.count('1')] = one_groups[x.count('1')] + [x]- ce qui compte, c'est qu'il y en ait +.

Je sais que tout le monde dit que a+=bc'est juste a=a+b, mais l'implémentation peut être différente pour l'optimisation - dans le cas des listes, +=c'est simplement .extendparce que nous savons que nous voulons notre résultat dans la variable actuelle, donc créer une nouvelle liste serait une perte de mémoire.

h4z3
la source
Ah, oui, compris. Je me souviens également d'avoir eu un problème similaire lorsque je voulais créer une liste 2D à l'aide de mylist = [[] * 5] * 5et comment l' mylist = [[] for x in range(5)] * 5aurais résolu. Juste pour une clarification rapide, d'après ce que j'ai compris, cela se produit en raison des variables pointant vers l'adresse mémoire de cette liste vide. Cela signifie-t-il également que le problème ne se produirait pas si j'utilisais des primitives à la place?
SpectraXCD
1
Oui, si vous avez utilisé des primitives, cela le résoudra, mais s'arrêtera one_groups[x.count('1')] += [x]car vous ne pouvez pas ajouter de liste à un type primitif. Une meilleure solution consiste à utiliser plutôt defaultdict.
Fakher Mokadem
4
en particulier, +appelle __add__et renvoie un nouvel objet, tandis qu'il +=appelle __iadd__et n'est pas obligé de renvoyer un nouvel objet
njzk2
8

Le problème est d'utiliser one_groups = dict.fromkeys(range(5), [])

(Cela transmet la même liste que la valeur à toutes les clés. Donc, si vous changez une valeur, vous les changez toutes)


Vous pouvez utiliser ceci à la place: one_groups = {i:[] for i in range(5)}

(Cette chose "exécutera" [] (ce qui équivaut à list ()) pour chaque clé, créant ainsi les valeurs avec des listes différentes.)

Hameda169
la source
6
Vous avez absolument raison, même si une explication serait vraiment utile. La différence entre les deux lignes n'est vraiment pas évidente.
Simon Fink
Oui, c'est ma mauvaise. désolé
Hameda169
4

Ceci est l'aide sur la fromkeysméthode de dict .

Aide sur les touches de fonction intégrées:

fromkeys (iterable, value = None, /) méthode de l'instance builtins.type Créez un nouveau dictionnaire avec les clés d'itérable et les valeurs définies sur value

Cela signifie que fromkeys acceptera une valeur, et même si c'est un appelable, il l'évaluera d'abord, puis attribuera cette valeur à toutes les touches dict.

Les listes sont modifiables en Python, et il affectera donc la même référence de liste vide et une modification les affectera toutes.

Utilisez plutôt defaultdict comme suit:

>>> from collections import defaultdict
>>> one_groups = defaultdict(list)
>>> for x in binaries:
      one_groups[x.count('1')] += [x]
>>> one_groups = dict(one_groups) # to stop default dict behavior

Cela acceptera les affectations à des clés non existantes et les valeurs seront par défaut des listes vides (dans ce cas).

Fakher Mokadem
la source