Wie lösche ich Elemente aus einem Wörterbuch, während ich darüber iteriere?

295

Ist es legitim, Elemente aus einem Wörterbuch in Python zu löschen, während Sie darüber iterieren?

Beispielsweise:

for k, v in mydict.iteritems():
   if k == val:
     del mydict[k]

Die Idee ist, Elemente, die eine bestimmte Bedingung nicht erfüllen, aus dem Wörterbuch zu entfernen, anstatt ein neues Wörterbuch zu erstellen, das eine Teilmenge des Wörterbuchs ist, über das iteriert wird.

Ist das eine gute Lösung? Gibt es elegantere / effizientere Wege?

Trilarion
quelle
1
Eine verwandte Frage mit sehr interessanten Antworten: stackoverflow.com/questions/9023078/… .
Max
Man hätte es leicht versuchen können. Wenn es fehlschlägt, ist es nicht legitim.
Trilarion
26
@Trilarion Man hätte es leicht versuchen können ... und leicht nichts Wertvolles lernen können. Wenn es gelingt, ist es nicht unbedingt legitim. Randfälle und unerwartete Vorbehalte gibt es zuhauf. Diese Frage ist für alle angehenden Pythonisten von nicht trivialem Interesse. Handwinkende Entlassung auf Befehl "Man hätte es leicht versuchen können!" ist nicht hilfreich und widerspricht dem neugierigen Geist der Stackoverflow-Untersuchung.
Cecil Curry
Nachdem ich die verwandte Frage von max durchgesehen habe , muss ich zustimmen. Sie möchten wahrscheinlich nur diese verstörend eingehende Frage und ihre gut geschriebenen Antworten lesen. Dein pythonischer Verstand wird geblasen.
Cecil Curry
1
@CecilCurry Wenn Sie sich nicht irren, testen Sie eine Idee selbst, bevor Sie sie hier präsentieren. Das war alles was ich vermitteln wollte. Entschuldigung, wenn es dadurch zu Störungen gekommen ist. Ich denke auch, dass es eine gute Frage ist und habe sie nicht abgelehnt. Die Antwort von Jochen Ritzel gefällt mir am besten. Ich denke nicht, dass man all diese Dinge tun muss, um sie im laufenden Betrieb zu löschen, wenn das Löschen in einem zweiten Schritt viel einfacher ist. Das sollte aus meiner Sicht der bevorzugte Weg sein.
Trilarion

Antworten:

305

BEARBEITEN:

Diese Antwort funktioniert nicht für Python3 und gibt eine RuntimeError.

RuntimeError: Das Wörterbuch hat während der Iteration seine Größe geändert.

Dies liegt daran, dass mydict.keys()ein Iterator keine Liste zurückgibt. Wie in den Kommentaren erwähnt, konvertieren Sie einfach mydict.keys()in eine Liste von list(mydict.keys())und es sollte funktionieren.


Ein einfacher Test in der Konsole zeigt, dass Sie ein Wörterbuch nicht ändern können, während Sie es durchlaufen:

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k, v in mydict.iteritems():
...    if k == 'two':
...        del mydict[k]
...
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

Wie in der Antwort von delnan angegeben, verursacht das Löschen von Einträgen Probleme, wenn der Iterator versucht, zum nächsten Eintrag überzugehen. Verwenden Sie stattdessen die keys()Methode, um eine Liste der Schlüssel abzurufen und damit zu arbeiten:

>>> for k in mydict.keys():
...    if k == 'two':
...        del mydict[k]
...
>>> mydict
{'four': 4, 'three': 3, 'one': 1}

Wenn Sie basierend auf dem Elementwert löschen müssen, verwenden Sie items()stattdessen die folgende Methode:

>>> for k, v in mydict.items():
...     if v == 3:
...         del mydict[k]
...
>>> mydict
{'four': 4, 'one': 1}
Blair
quelle
53
Beachten Sie, dass dict.items () in Python 3 einen Iterator zurückgibt (und dict.iteritems () weg ist).
Tim Lesher
83
Um auf @ TimLesher Kommentar näher zu erläutern ... Dies wird in Python 3 NICHT funktionieren.
Max
99
Um die Ausarbeitung von @ max zu erläutern, funktioniert es, wenn Sie den obigen Code mit 2to3 konvertieren. Einer der Standardfixierer lässt die Schleife so aussehen, wie sie for k, v in list(mydict.items()):in Python 3 gut funktioniert. Gleiches gilt für keys()Werden list(keys()).
Walter Mundt
8
Das funktioniert nicht. Ich erhalte eine Fehlermeldung:RuntimeError: dictionary changed size during iteration
Tomáš Zato - Reinstate Monica
15
@ TomášZato, wie Walter betonte, für python3 müssen Sie verwenden, for k in list(mydict.keys()): da python3 die keys () -Methode zu einem Iterator macht und das Löschen von diktierten Elementen während der Iteration nicht zulässt. Durch Hinzufügen eines list () -Aufrufs verwandeln Sie den keys () -Iterator in eine Liste. Wenn Sie sich also im Hauptteil der for-Schleife befinden, iterieren Sie nicht mehr über das Wörterbuch selbst.
Geoff Crompton
89

Sie können dies auch in zwei Schritten tun:

remove = [k for k in mydict if k == val]
for k in remove: del mydict[k]

Mein Lieblingsansatz ist normalerweise, einfach ein neues Diktat zu machen:

# Python 2.7 and 3.x
mydict = { k:v for k,v in mydict.items() if k!=val }
# before Python 2.7
mydict = dict((k,v) for k,v in mydict.iteritems() if k!=val)
Jochen Ritzel
quelle
11
@senderle: Seit 2.7 eigentlich.
Jochen Ritzel
5
Der Diktatverständnisansatz erstellt eine Kopie des Wörterbuchs. Zum Glück werden die Werte zumindest nicht tief kopiert, sondern nur verknüpft. Wenn Sie dennoch viele Schlüssel haben, kann dies schlecht sein. Aus diesem Grund mag ich den removeLoop-Ansatz mehr.
Max
1
Sie können auch die Schritte kombinieren:for k in [k for k in mydict if k == val]: del mydict[k]
AXO
Die erste Lösung ist die einzige effiziente Lösung für große Diktate in diesem Thread - da keine Kopie in voller Länge erstellt wird.
kxr
21

Sie können eine Sammlung nicht ändern, während Sie sie wiederholen. Auf diese Weise liegt der Wahnsinn - vor allem, wenn Sie das aktuelle Element löschen und löschen könnten, müsste der Iterator weitermachen (+1) und der nächste Aufruf nextwürde Sie darüber hinaus führen (+2), also würden Sie Überspringen Sie am Ende ein Element (das direkt hinter dem von Ihnen gelöschten). Sie haben zwei Möglichkeiten:

  • Kopieren Sie alle Schlüssel (oder Werte oder beides, je nachdem, was Sie benötigen) und wiederholen Sie diese. Sie können hierfür .keys()et al. Verwenden (übergeben Sie in Python 3 den resultierenden Iterator an list). Könnte jedoch räumlich sehr verschwenderisch sein.
  • Iterieren Sie mydictwie gewohnt und speichern Sie die zu löschenden Schlüssel in einer separaten Sammlung to_delete. Wenn Sie mit dem Iterieren fertig sind mydict, löschen Sie alle Elemente to_deleteaus mydict. Spart beim ersten Ansatz etwas Platz (abhängig davon, wie viele Schlüssel gelöscht wurden und wie viele verbleiben), erfordert aber auch einige weitere Zeilen.

quelle
You can't modify a collection while iterating it.Dies ist nur für Diktate und Freunde richtig, aber Sie können Listen während der Iteration ändern:L = [1,2,None,4,5] <\n> for n,x in enumerate(L): <\n\t> if x is None: del L[n]
Nils Lindemann
3
@Nils Es wird keine Ausnahme ausgelöst, aber es ist immer noch falsch. Beachten Sie: codepad.org/Yz7rjDVT - siehe z. B. stackoverflow.com/q/6260089/395760 für eine Erklärung
Habe mich hierher gebracht. Immer noch can'tist nur für Diktat und Freunde richtig, während es shouldn'tfür Listen sein sollte.
Nils Lindemann
21

Iterieren Sie stattdessen über eine Kopie, z. B. die von items():

for k, v in list(mydict.items()):
Ignacio Vazquez-Abrams
quelle
1
Das macht nicht viel Sinn - dann können Sie nicht del vdirekt, also haben Sie eine Kopie von jedem v erstellt, das Sie nie verwenden werden, und Sie müssen sowieso per Schlüssel auf die Elemente zugreifen. dict.keys()ist eine bessere Wahl.
Jscs
2
@Josh: Es hängt alles davon ab, wie viel Sie vals Kriterium für das Löschen verwenden müssen.
Ignacio Vazquez-Abrams
3
Gibt unter Python 3 dict.items()eher einen Iterator als eine Kopie zurück. Siehe Kommentar für Blair ‚s Antwort , die (leider) auch Python 2 Semantik annimmt.
Cecil Curry
11

Es ist am saubersten zu verwenden list(mydict):

>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k in list(mydict):
...     if k == 'three':
...         del mydict[k]
... 
>>> mydict
{'four': 4, 'two': 2, 'one': 1}

Dies entspricht einer parallelen Struktur für Listen:

>>> mylist = ['one', 'two', 'three', 'four']
>>> for k in list(mylist):                            # or mylist[:]
...     if k == 'three':
...         mylist.remove(k)
... 
>>> mylist
['one', 'two', 'four']

Beide arbeiten in Python2 und Python3.

rsanden
quelle
Dies ist nicht gut, wenn Ihr Datensatz groß ist. Dies kopiert alle Objekte im Speicher, oder?
AFP_555
1
@ AFP_555 Ja - mein Ziel hier ist sauberer, paralleler, pythonischer Code. Wenn Sie Speichereffizienz benötigen, ist der beste mir bekannte Ansatz, entweder eine Liste der zu löschenden Schlüssel oder ein neues Diktat der zu speichernden Elemente zu iterieren und zu erstellen. Schönheit ist meine Priorität bei Python; Für große Datenmengen verwende ich Go oder Rust.
Rsanden
9

Sie können ein Wörterbuchverständnis verwenden.

d = {k:d[k] for k in d if d[k] != val}

Aaron
quelle
Dies ist die pythonischste.
Yehosef
Es wird jedoch ein neues Wörterbuch erstellt, anstatt Änderungen vorzunehmen d.
Aristide
9

Wenn Sie mit python3 auf dic.keys () iterieren, wird der Fehler in der Wörterbuchgröße ausgelöst. Sie können diese alternative Methode verwenden:

Getestet mit Python3 funktioniert es einwandfrei und der Fehler " Wörterbuch hat während der Iteration die Größe geändert " wird nicht ausgelöst :

my_dic = { 1:10, 2:20, 3:30 }
# Is important here to cast because ".keys()" method returns a dict_keys object.
key_list = list( my_dic.keys() )

# Iterate on the list:
for k in key_list:
    print(key_list)
    print(my_dic)
    del( my_dic[k] )


print( my_dic )
# {}
glihm
quelle
4

Sie können zuerst eine Liste der zu löschenden Schlüssel erstellen und dann über diese Liste iterieren, um sie zu löschen.

dict = {'one' : 1, 'two' : 2, 'three' : 3, 'four' : 4}
delete = []
for k,v in dict.items():
    if v%2 == 1:
        delete.append(k)
for i in delete:
    del dict[i]
Pob
quelle
Es ist eher ein Duplikat von @ Ritzels 1. Lösung (effizient bei großen Diktaten ohne vollständige Kopie). Obwohl ein "langes Lesen" ohne Listenverständnis. Doch ist es doch möglicherweise schneller?
kxr
3

Es gibt eine Möglichkeit, die geeignet sein kann, wenn sich die zu löschenden Elemente immer am "Anfang" der Diktationsiteration befinden

while mydict:
    key, value = next(iter(mydict.items()))
    if should_delete(key, value):
       del mydict[key]
    else:
       break

Der "Anfang" ist garantiert nur für bestimmte Python-Versionen / -Implementierungen konsistent. Zum Beispiel aus den Neuerungen in Python 3.7

Die Beibehaltung der Einfügungsreihenfolge von diktierten Objekten wurde als offizieller Bestandteil der Python-Sprachspezifikation deklariert.

Auf diese Weise wird eine Kopie des Diktats vermieden, die viele der anderen Antworten zumindest in Python 3 nahe legen.

Michal Charemza
quelle
1

Ich habe die oben genannten Lösungen in Python3 ausprobiert, aber diese scheint die einzige zu sein, die für mich funktioniert, wenn Objekte in einem Diktat gespeichert werden. Grundsätzlich erstellen Sie eine Kopie Ihres dict () und wiederholen diese, während Sie die Einträge in Ihrem ursprünglichen Wörterbuch löschen.

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])
Jason Landbridge
quelle