Wie vermeide ich den Fehler "RuntimeError: Wörterbuch hat während der Iteration die Größe geändert"?

258

Ich habe alle anderen Fragen mit demselben Fehler überprüft, aber keine hilfreiche Lösung gefunden = /

Ich habe ein Wörterbuch mit Listen:

d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}

in dem einige der Werte leer sind. Am Ende der Erstellung dieser Listen möchte ich diese leeren Listen entfernen, bevor ich mein Wörterbuch zurückgebe. Derzeit versuche ich dies wie folgt zu tun:

for i in d:
    if not d[i]:
        d.pop(i)

Dies gibt mir jedoch den Laufzeitfehler. Mir ist bewusst, dass Sie keine Elemente in einem Wörterbuch hinzufügen / entfernen können, während Sie es durchlaufen ... was wäre dann ein Weg, dies zu umgehen?

user1530318
quelle

Antworten:

455

In Python 2.x erstellt der Aufruf keyseine Kopie des Schlüssels, über den Sie iterieren können, während Sie Folgendes ändern dict:

for i in d.keys():

Beachten Sie, dass dies in Python 3.x nicht funktioniert, da keysein Iterator anstelle einer Liste zurückgegeben wird.

Eine andere Möglichkeit besteht darin list, das Erstellen einer Kopie der Schlüssel zu erzwingen. Dieser funktioniert auch in Python 3.x:

for i in list(d):
Mark Byers
quelle
1
Ich glaube, Sie meinten "Anruf keysmacht eine Kopie der Schlüssel , über die Sie iterieren können", auch bekannt als die pluralSchlüssel, oder? Wie kann man sonst über einen einzelnen Schlüssel iterieren? Ich bin übrigens nicht picken, bin wirklich interessiert zu wissen, ob das tatsächlich Schlüssel oder Schlüssel ist
HighOnMeat
6
Oder Tupel statt Liste, da es schneller ist.
Brambor
8
Um das Verhalten von Python 3.x zu verdeutlichen, gibt d.keys () eine iterable (keine Iterator) zurück, was bedeutet, dass es sich um eine direkte Ansicht der Schlüssel des Wörterbuchs handelt. Die Verwendung for i in d.keys()funktioniert in Python 3.x im Allgemeinen , aber da sie über eine iterierbare Ansicht der Schlüssel des Wörterbuchs iteriert, führt das Aufrufen d.pop()während der Schleife zu demselben Fehler, den Sie gefunden haben. for i in list(d)emuliert das etwas ineffiziente Python 2-Verhalten beim Kopieren der Schlüssel in eine Liste vor dem Iterieren für besondere Umstände wie Ihre.
Michael Krebs
Ihre Python3-Lösung funktioniert nicht, wenn Sie ein Objekt im inneren Diktat löschen möchten. Zum Beispiel haben Sie ein DIC A und ein Diktat B in Diktat A. Wenn Sie ein Objekt in Diktat B löschen möchten, tritt ein Fehler auf
Ali-T
1
Erstellt in python3.x list(d.keys())dieselbe Ausgabe wie beim list(d)Aufrufen listvon a und dictgibt die Schlüssel zurück. Der keysAnruf (obwohl nicht so teuer) ist unnötig.
Sean Breckenridge
46

Verwenden Sie einfach das Wörterbuchverständnis, um die relevanten Elemente in ein neues Diktat zu kopieren

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = { k : v for k,v in d.iteritems() if v}
>>> d
{'a': [1], 'b': [1, 2]}

Dafür in Python 3

>>> d
{'a': [1], 'c': [], 'b': [1, 2], 'd': []}
>>> d = { k : v for k,v in d.items() if v}
>>> d
{'a': [1], 'b': [1, 2]}
Maria Zverina
quelle
11
d.iteritems()gab mir einen Fehler. Ich habe d.items()stattdessen - mit Python3
wcyn
4
Dies funktioniert für das in der Frage von OP aufgeworfene Problem. Jeder, der hierher gekommen ist, nachdem er diesen RuntimeError in Multithread-Code getroffen hat, muss sich darüber im Klaren sein, dass CPythons GIL auch in der Mitte des Listenverständnisses veröffentlicht werden kann und Sie es anders beheben müssen.
Yirkha
40

Sie müssen nur "copy" verwenden:

Auf diese Weise iterieren Sie über die ursprünglichen Wörterbuchfelder und können im laufenden Betrieb das gewünschte Diktat (d dict) ändern. Es ist Arbeit an jeder Python-Version, also ist es klarer.

In [1]: d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}

In [2]: for i in d.copy():
   ...:     if not d[i]:
   ...:         d.pop(i)
   ...:         

In [3]: d
Out[3]: {'a': [1], 'b': [1, 2]}
Alon Elharar
quelle
Hat funktioniert! Danke dir.
Guilherme Carvalho Lithg
13

Ich würde versuchen, das Einfügen leerer Listen zu vermeiden, würde aber im Allgemeinen Folgendes verwenden:

d = {k: v for k,v in d.iteritems() if v} # re-bind to non-empty

Wenn vor 2.7:

d = dict( (k, v) for k,v in d.iteritems() if v )

oder nur:

empty_key_vals = list(k for k in k,v in d.iteritems() if v)
for k in empty_key_vals:
    del[k]
Jon Clements
quelle
+1: Die letzte Option ist interessant, da nur die Schlüssel der Elemente kopiert werden, die gelöscht werden müssen. Dies kann zu einer besseren Leistung führen, wenn nur eine kleine Anzahl von Elementen im Verhältnis zur Größe des Diktats gelöscht werden muss.
Mark Byers
@MarkByers yup - und wenn eine große Anzahl dies tut, ist es eine bessere Option, das Diktat an ein neues zu binden, das gefiltert wird. Es ist immer die Erwartung, wie die Struktur funktionieren soll
Jon Clements
4
Eine Gefahr beim erneuten Binden besteht darin, dass irgendwo im Programm ein Objekt vorhanden ist, das einen Verweis auf das alte Diktat enthält, das die Änderungen nicht sieht. Wenn Sie sicher sind, dass dies nicht der Fall ist, dann ist dies sicher ein vernünftiger Ansatz, aber es ist wichtig zu verstehen, dass dies nicht ganz das gleiche ist wie das Ändern des ursprünglichen Diktats.
Mark Byers
@ MarkByers extrem guter Punkt - Sie und ich wissen das (und unzählige andere), aber es ist nicht für alle offensichtlich. Und ich werde Geld auf den Tisch legen, es hat dich nicht auch in den Hintern gebissen :)
Jon Clements
Es ist sehr gut zu vermeiden, die leeren Einträge einzufügen.
Magnus Bodin
12

Für Python 3:

{k:v for k,v in d.items() if v}
Ucyo
quelle
Schön und prägnant. Hat auch in Python 2.7 für mich gearbeitet.
donrondadon
6

Sie können ein Wörterbuch nicht durchlaufen, während es sich während der for-Schleife ändert. Machen Sie ein Casting, um diese Liste aufzulisten und zu durchlaufen, es funktioniert für mich.

    for key in list(d):
        if not d[key]: 
            d.pop(key)
Alvaro Romero Diaz
quelle
1

In solchen Situationen möchte ich eine tiefe Kopie erstellen und diese Kopie durchlaufen, während ich das ursprüngliche Diktat ändere.

Wenn sich das Suchfeld in einer Liste befindet, können Sie in der for-Schleife der Liste auflisten und dann die Position als Index angeben, um auf das Feld im ursprünglichen Diktat zuzugreifen.

Ajayi Oluwaseun Emmanuel
quelle
1

Das hat bei mir funktioniert:

dict = {1: 'a', 2: '', 3: 'b', 4: '', 5: '', 6: 'c'}
for key, value in list(dict.items()):
    if (value == ''):
        del dict[key]
print(dict)
# dict = {1: 'a', 3: 'b', 6: 'c'}  

Wenn Sie die Wörterbuchelemente in eine Liste umwandeln, wird eine Liste der Elemente erstellt, sodass Sie darüber iterieren und das vermeiden können RuntimeError.

Singrium
quelle
1

dictc = {"stName": "asas"} keys = dictc.keys () für die Eingabe in die Liste (Schlüssel): dictc [key.upper ()] = 'Neuer Wert' print (str (dictc))

vaibhav.patil
quelle
0

Der Grund für den Laufzeitfehler ist, dass Sie eine Datenstruktur nicht durchlaufen können, während sich ihre Struktur während der Iteration ändert.

Eine Möglichkeit, um das zu erreichen, wonach Sie suchen, besteht darin, die zu entfernenden Schlüssel mithilfe der Liste anzuhängen und dann den identifizierten Schlüssel mithilfe der Pop-Funktion im Wörterbuch zu entfernen, während Sie die Liste durchlaufen.

d = {'a': [1], 'b': [1, 2], 'c': [], 'd':[]}
pop_list = []

for i in d:
        if not d[i]:
                pop_list.append(i)

for x in pop_list:
        d.pop(x)
print (d)
Rohit
quelle
0

Python 3 erlaubt kein Löschen beim Iterieren des Wörterbuchs (unter Verwendung der obigen Schleife). Es gibt verschiedene Alternativen; Eine einfache Möglichkeit besteht darin, die folgende Zeile zu ändern

for i in x.keys():

Mit

for i in list(x)
Hasham
quelle