Perché dict.get (key) ha funzionato ma non dict [key]?


17

Sto cercando di raggruppare le stringhe binarie di determinati numeri in base al numero di 1 presenti nella stringa.

Questo non funziona:

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]

Il dizionario previsto one_groupsdeve essere

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

Ma capisco

{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']}

Finora l'unica cosa che ha funzionato è se uso one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]invece dione_groups[x.count('1')] += [x]

Ma perché è così? Se ricordo bene, non dict[key]dovrebbe restituire il valore di quel dizionario, simile a come dict.get(key)funziona? Ho visto questa discussione Perché dict.get (chiave) invece di dict [chiave]? ma non ha risposto alla mia domanda per questo caso particolare, dal momento che so per certo che il programma non è pensato per ottenere ilKeyError

Ho anche provato, one_groups[x.count('1')].append(x)ma neanche questo funziona.


8
getrestituisce Nonese la chiave non esiste o qualsiasi valore predefinito fornito, mentre l'operatore indice []genera un errore se la chiave non esiste.
adnanmuttaleb,

Sidenote, bin(x)[2:].rjust(4, '0')può essere semplificato '{:0>4b}'.format(x).
wjandrea,

1
A proposito, aiuta a fare un esempio riproducibile minimo . In questo caso il modo in cui fai binariesnon è rilevante per la domanda, quindi potresti semplicemente fornire il suo valore.
wjandrea,

1
Questo risponde alla tua domanda? dict.fromkeys indicano tutti lo stesso elenco
Georgy,

Risposte:


24

Il problema è la mutabilità:

one_groups = dict.fromkeys(range(5), [])- questo passa la stessa lista come valore a tutte le chiavi . Quindi se cambi un valore, li cambi tutti.

È praticamente lo stesso che dire:

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

Se vuoi usare un nuovo elenco, devi farlo in un ciclo - un forciclo esplicito o in una comprensione di dict:

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

Questa cosa "eseguirà" [](che equivale a list()) per ogni chiave, rendendo così i valori con elenchi diversi.


Perché getfunziona Perché prendi esplicitamente l'elenco corrente, ma +crea un nuovo elenco di risultati. E non importa se lo è one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]o one_groups[x.count('1')] = one_groups[x.count('1')] + [x]- ciò che conta è che ci sia +.

So come tutti dicono che a+=bè giusto a=a+b, ma l'implementazione potrebbe essere diversa per l'ottimizzazione - nel caso degli elenchi, +=è solo .extendperché sappiamo che vogliamo il nostro risultato nella variabile corrente, quindi la creazione di un nuovo elenco sarebbe uno spreco di memoria.


Ah, sì, capito. Ricordo anche di avere un problema simile quando volevo creare un elenco 2D usando mylist = [[] * 5] * 5e come l' mylist = [[] for x in range(5)] * 5avrei risolto. Solo per un rapido chiarimento, da quanto ho capito, ciò accade a causa delle variabili che puntano all'indirizzo di memoria di quell'elenco vuoto. Questo significa anche che il problema non si verificherebbe se invece usassi le primitive?
SpectraXCD,

1
Sì, se hai usato le primitive questo lo risolverà, ma si romperà one_groups[x.count('1')] += [x]perché non puoi aggiungere un elenco a un tipo primitivo. Una soluzione migliore è invece utilizzare defaultdict.
Fakher Mokadem,

4
in particolare, +chiama __add__e restituisce un nuovo oggetto, mentre +=chiama __iadd__, e non è necessario per restituire un nuovo oggetto
njzk2

8

Il problema sta usando one_groups = dict.fromkeys(range(5), [])

(Questo passa lo stesso elenco di valori a tutte le chiavi. Quindi, se si cambia un valore, si cambiano tutti)


È possibile utilizzare questo invece: one_groups = {i:[] for i in range(5)}

(Questa cosa "eseguirà" [] (che equivale a list ()) per ogni chiave, rendendo così i valori con liste diverse.)


6
Hai assolutamente ragione, anche se una spiegazione sarebbe davvero utile. Non è davvero ovvio quale sia la differenza tra le due linee.
Simon Fink,

Sì, è il mio male. scusa
Hameda169

4

Questo è l'aiuto sul fromkeysmetodo di dict .

Aiuto su tasti funzione integrati:

metodo fromkeys (iterable, value = None, /) dell'istanza builtins.type Crea un nuovo dizionario con chiavi da iterable e valori impostati su value

Ciò significa che i tokeys accetteranno un valore e anche se è un callable, lo valuterà prima e quindi assegnerà quel valore a tutte le chiavi dict.

Gli elenchi sono modificabili in Python, quindi assegnerà lo stesso riferimento di elenco vuoto e una modifica li influenzerà tutti.

Usa defaultdict invece come questo:

>>> 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

Questo accetterà assegnazioni a chiavi inesistenti e i valori verranno automaticamente impostati su elenchi vuoti (in questo caso).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.