Warum hat dict.get (key) funktioniert, aber nicht dict [key]?

17

Ich versuche, die binären Zeichenfolgen bestimmter Zahlen basierend auf der Anzahl der Einsen in der Zeichenfolge zu gruppieren.

Das funktioniert nicht:

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]

Das erwartete Wörterbuch one_groupsmuss sein

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

Aber ich verstehe

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

Bisher ist die einzige Sache , die funktioniert hat, wenn ich one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]stattone_groups[x.count('1')] += [x]

Aber warum ist das so? Wenn ich mich richtig erinnere, dict[key]soll der Wert dieses Wörterbuchs nicht zurückgegeben werden, ähnlich wie es dict.get(key)funktioniert? Ich habe diesen Thread gesehen. Warum dict.get (Schlüssel) anstelle von dict [Schlüssel]? aber es hat meine Frage für diesen speziellen Fall nicht beantwortet, da ich sicher weiß, dass das Programm nicht dazu gedacht ist, das zu bekommenKeyError

Ich habe es auch versucht, one_groups[x.count('1')].append(x)aber das funktioniert auch nicht.

SpectraXCD
quelle
8
getRückgabe, Nonewenn der Schlüssel nicht vorhanden ist oder ein angegebener Standardwert, während der []Indexoperator einen Fehler auslöst, wenn der Schlüssel nicht vorhanden ist.
Adnanmuttaleb
Nebenbemerkung, bin(x)[2:].rjust(4, '0')kann vereinfacht werden '{:0>4b}'.format(x).
Wjandrea
1
Übrigens hilft es, ein minimal reproduzierbares Beispiel zu machen . In diesem Fall binariesist Ihre Vorgehensweise für die Frage nicht relevant, sodass Sie nur ihren Wert angeben können.
Wjandrea
1
Beantwortet das deine Frage? dict.fromkeys alle zeigen auf die gleiche Liste
Georgy

Antworten:

24

Das Problem ist die Veränderlichkeit:

one_groups = dict.fromkeys(range(5), [])- Dadurch wird allen Schlüsseln dieselbe Liste als Wert übergeben . Wenn Sie also einen Wert ändern, ändern Sie alle.

Es ist im Grunde das gleiche wie zu sagen:

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

Wenn Sie eine neue Liste verwenden möchten, müssen Sie dies in einer Schleife tun - entweder in einer expliziten forSchleife oder in einem Diktatverständnis:

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

Dieses Ding wird für jeden Schlüssel "ausgeführt" [](was gleich ist list()), wodurch die Werte mit unterschiedlichen Listen erstellt werden.


Warum getarbeitet? Weil Sie explizit die aktuelle Liste nehmen, aber +eine neue Ergebnisliste erstellen. Und es spielt keine Rolle, ob es ist one_groups[x.count('1')] = one_groups.get(x.count('1')) + [x]oder one_groups[x.count('1')] = one_groups[x.count('1')] + [x]- was zählt, ist, dass es gibt +.

Ich weiß, wie jeder sagt, dass a+=bes gerecht ist a=a+b, aber die Implementierung kann für die Optimierung unterschiedlich sein - im Fall von Listen +=nur, .extendweil wir wissen, dass wir unser Ergebnis in der aktuellen Variablen haben wollen, so dass das Erstellen einer neuen Liste Speicherverschwendung wäre.

h4z3
quelle
Ah ja, verstanden. Ich erinnere mich auch an ein ähnliches Problem, als ich eine 2D-Liste mit erstellen wollte mylist = [[] * 5] * 5und wie mylist = [[] for x in range(5)] * 5ich es behoben hätte. Nur zur schnellen Verdeutlichung geschieht dies, wie ich verstanden habe, aufgrund der Variablen, die auf die Speicheradresse dieser leeren Liste verweisen. Bedeutet dies auch, dass das Problem nicht auftreten würde, wenn ich stattdessen Grundelemente verwenden würde?
SpectraXCD
1
Ja, wenn Sie Grundelemente verwendet haben, wird dies behoben, aber es wird unterbrochen, one_groups[x.count('1')] += [x]da Sie einem Grundelementtyp keine Liste hinzufügen können. Eine bessere Lösung ist die Verwendung von defaultdict.
Fakher Mokadem
4
Insbesondere +Anrufe __add__und gibt ein neues Objekt, während +=Anrufe __iadd__, und ist nicht erforderlich , um ein neues Objekt zurück
njzk2
8

Das Problem ist mit one_groups = dict.fromkeys(range(5), [])

(Dadurch wird allen Schlüsseln dieselbe Liste als Wert übergeben. Wenn Sie also einen Wert ändern, ändern Sie alle.)


Sie können dies stattdessen verwenden: one_groups = {i:[] for i in range(5)}

(Dieses Ding "führt" [] aus (was list () entspricht) für jeden Schlüssel, wodurch die Werte mit unterschiedlichen Listen erstellt werden.)

Hameda169
quelle
6
Sie haben absolut Recht, obwohl eine Erklärung sehr hilfreich wäre. Es ist wirklich nicht klar, was der Unterschied zwischen den beiden Zeilen ist.
Simon Fink
Ja, es ist mein schlechtes. Entschuldigung
Hameda169
4

Dies ist die Hilfe zur fromkeysMethode von dict .

Hilfe zur integrierten Funktion von Schlüsseln:

fromkeys (iterable, value = None, /) Methode der Instanz von builtins.type Erstellen Sie ein neues Wörterbuch mit Schlüsseln von iterable und Werten, die auf value gesetzt sind

Das heißt, fromkeys akzeptiert einen Wert, und selbst wenn er aufrufbar ist, wertet er ihn zuerst aus und weist diesen Wert dann allen diktierten Schlüsseln zu.

Listen sind in Python veränderbar, sodass dieselbe leere Listenreferenz zugewiesen wird und eine Änderung sie alle betrifft.

Verwenden Sie stattdessen defaultdict wie folgt:

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

Dies akzeptiert Zuweisungen zu nicht vorhandenen Schlüsseln und Werte werden standardmäßig zu leeren Listen (in diesem Fall).

Fakher Mokadem
quelle