Come eliminare elementi da un dizionario mentre si scorre su di esso?


295

È legittimo eliminare elementi da un dizionario in Python mentre si scorre su di esso?

Per esempio:

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

L'idea è quella di rimuovere elementi che non soddisfano una determinata condizione dal dizionario, invece di creare un nuovo dizionario che è un sottoinsieme di quello su cui viene ripetuto.

È una buona soluzione? Ci sono modi più eleganti / efficienti?


1
Una domanda correlata con risposte molto interessanti: stackoverflow.com/questions/9023078/… .
massimo

Uno avrebbe potuto provare facilmente. Se fallisce, non è legittimo.
Trilarion,

26
@Trilarion Uno avrebbe potuto provare facilmente ... e facilmente imparato nulla di valore. Se ci riesce, non è necessariamente legittimo. I casi limite e le avvertenze inattese abbondano. Questa domanda è di interesse non banale per tutti gli aspiranti pitonisti. Licenziamento agitando la mano nell'ordine di "Uno avrebbe potuto provare facilmente!" è inutile e contrario allo spirito inquisitore dell'indagine stackoverflow.
Cecil Curry,

Dopo aver esaminato la domanda relativa a Max , devo concordare. Probabilmente vuoi solo esaminare questa domanda inquietantemente approfondita e le sue risposte ben scritte. La tua mente Pythonic sarà spazzata via.
Cecil Curry,

1
@CecilCurry Testare un'idea per te prima di presentarla qui è un po 'nello spirito di StackOverflow se non sbaglio. Questo era tutto ciò che volevo trasmettere. Scusate se c'è stato qualche disturbo a causa di ciò. Inoltre penso che sia una buona domanda e non l'abbia votata. Mi piace di più la risposta di Jochen Ritzel . Non penso che uno debba fare tutte quelle cose per cancellare al volo quando l'eliminazione in un secondo passaggio è molto più semplice. Questo dovrebbe essere il modo preferito a mio avviso.
Trilarion,

Risposte:


305

MODIFICARE:

Questa risposta non funzionerà per Python3 e fornirà a RuntimeError.

RuntimeError: il dizionario ha cambiato dimensione durante l'iterazione.

Ciò accade perché mydict.keys()restituisce un iteratore non un elenco. Come sottolineato nei commenti, è sufficiente convertirlo mydict.keys()in un elenco list(mydict.keys())e dovrebbe funzionare.


Un semplice test nella console mostra che non è possibile modificare un dizionario mentre si scorre su di esso:

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

Come indicato nella risposta di delnan, l'eliminazione delle voci causa problemi quando l'iteratore tenta di passare alla voce successiva. Invece, usa il keys()metodo per ottenere un elenco delle chiavi e lavorare con quello:

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

Se è necessario eliminare in base al valore degli elementi, utilizzare items()invece il metodo:

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

53
Nota che in Python 3, dict.items () restituisce un iteratore (e dict.iteritems () non c'è più).
Tim Lesher,

83
Per approfondire il commento di @TimLesher ... NON funzionerà in Python 3.
max

99
Per elaborare l'elaborazione di @ max, funzionerà se converti il ​​codice sopra con 2to3. Uno dei fissatori predefiniti farà apparire il ciclo for k, v in list(mydict.items()):che funziona bene in Python 3. Lo stesso per keys()diventare list(keys()).
Walter Mundt,

8
Questo non funziona Viene visualizzato un errore:RuntimeError: dictionary changed size during iteration
Tomáš Zato - Ripristina Monica il

15
@ TomášZato come ha sottolineato Walter, per python3 devi usare for k in list(mydict.keys()): come python3 rende il metodo keys () un iteratore e non consente inoltre di eliminare gli elementi dict durante l'iterazione. Aggiungendo una chiamata list () si trasforma l'iteratore keys () in un elenco. Quindi, quando ti trovi nel corpo del ciclo for, non stai più ripetendo il dizionario stesso.
Geoff Crompton,

89

Puoi anche farlo in due passaggi:

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

Il mio approccio preferito è di solito solo fare un nuovo dict:

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

11
@senderle: dal 2.7 in realtà.
Jochen Ritzel,

5
L'approccio di comprensione del dict crea una copia del dizionario; per fortuna i valori almeno non vengono copiati in profondità, ma solo collegati. Tuttavia, se hai molte chiavi, potrebbe essere un problema. Per questo motivo, mi piace di più l' removeapproccio ad anello.
max

1
È inoltre possibile combinare i passaggi:for k in [k for k in mydict if k == val]: del mydict[k]
AXO

la prima soluzione è l'unica efficiente su dicts grandi in questo thread finora - in quanto non ne fa una copia integrale.
kxr,

21

Non è possibile modificare una raccolta durante l'iterazione. In questo modo si cela la follia - in particolare, se ti fosse permesso di eliminare ed eliminare l'elemento corrente, l'iteratore dovrebbe passare (+1) e la chiamata successiva a nextportarti oltre (+2), quindi dovresti finisci per saltare un elemento (quello proprio dietro quello che hai eliminato). Hai due opzioni:

  • Copia tutte le chiavi (o i valori, o entrambi, a seconda delle necessità), quindi esegui l'iterazione su quelle. Puoi usare .keys()et al per questo (in Python 3, passa l'iteratore risultante a list). Potrebbe essere molto dispendioso in termini di spazio.
  • Scorri mydictcome al solito, salvando le chiavi per eliminarle in una raccolta separata to_delete. Al termine dell'iterazione mydict, elimina tutti gli elementi to_deleteda mydict. Salva un po 'di spazio (a seconda del numero di chiavi cancellate e di quanti rimangono) nel primo approccio, ma richiede anche qualche riga in più.

You can't modify a collection while iterating it.questo è giusto per dicts e amici, ma è possibile modificare gli elenchi durante l'iterazione: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 Non genera un'eccezione ma è ancora errato. Osservare: codepad.org/Yz7rjDVT - vedi ad esempio stackoverflow.com/q/6260089/395760 per una spiegazione

Mi ha portato qui. È ancora can'tcorretto solo per dict e amici, mentre dovrebbe essere shouldn'tper le liste.
Nils Lindemann,

21

Scorri invece su una copia, come quella restituita da items():

for k, v in list(mydict.items()):

1
Non ha molto senso - quindi non puoi del vdirettamente, quindi hai fatto una copia di ogni v che non userai mai e devi comunque accedere agli elementi con la chiave. dict.keys()è una scelta migliore.
jscs,

2
@Josh: dipende tutto da quanto dovrai utilizzare vcome criterio per la cancellazione.
Ignacio Vazquez-Abrams,

3
In Python 3, dict.items()restituisce un iteratore anziché una copia. Vedi il commento per la risposta di Blair , che (purtroppo) assume anche la semantica di Python 2.
Cecil Curry,

11

È più pulito da usare 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}

Ciò corrisponde a una struttura parallela per gli elenchi:

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

Entrambi funzionano in python2 e python3.


Questo non va bene nel caso in cui il set di dati sia di grandi dimensioni. Questo sta copiando tutti gli oggetti in memoria, giusto?
AFP_555,

1
@ AFP_555 Sì, il mio obiettivo qui è un codice pulito, parallelo e pitonico. Se hai bisogno di efficienza della memoria, l'approccio migliore che conosco è quello di iterare e creare un elenco di chiavi da eliminare o un nuovo dettato di elementi da salvare. La bellezza è la mia priorità con Python; per grandi set di dati sto usando Go o Rust.
sabato

9

Puoi usare una comprensione del dizionario.

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


Questo è il più Pythonic.
Yehosef,

Ma crea un nuovo dizionario invece di modificarlo dsul posto.
Aristide,

9

Con python3, iterando su dic.keys () aumenterà l'errore di dimensione del dizionario. Puoi usare questo modo alternativo:

Testato con python3, funziona benissimo e non viene generato l'errore "Il dizionario ha cambiato dimensione durante l'iterazione ":

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 )
# {}

4

È possibile innanzitutto creare un elenco di chiavi da eliminare, quindi scorrere su tale elenco eliminandole.

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]

È piuttosto un duplicato della prima soluzione di @ Ritzel (efficiente su grandi dicts senza copia completa). Sebbene una "lettura lunga" senza lista comprensione. Tuttavia è forse più veloce?
kxr,

3

C'è un modo che può essere adatto se gli elementi che si desidera eliminare sono sempre all'inizio dell'iterazione di dettatura

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

L '"inizio" è garantito per essere coerente solo per alcune versioni / implementazioni di Python. Ad esempio da Novità di Python 3.7

la natura di conservazione dell'ordine di inserimento degli oggetti dict è stata dichiarata parte ufficiale delle specifiche del linguaggio Python.

In questo modo si evita una copia del dict suggerito da molte altre risposte, almeno in Python 3.


1

Ho provato le soluzioni di cui sopra in Python3 ma questa sembra essere l'unica che funziona per me durante la memorizzazione di oggetti in un dict. Fondamentalmente fai una copia del tuo dict () e ripeti quello mentre cancelli le voci nel tuo dizionario originale.

        tmpDict = realDict.copy()
        for key, value in tmpDict.items():
            if value:
                del(realDict[key])
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.