Come evitare l'errore "RuntimeError: il dizionario ha modificato la dimensione durante l'iterazione"?


258

Ho verificato tutte le altre domande con lo stesso errore ma non ho trovato alcuna soluzione utile = /

Ho un dizionario di elenchi:

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

in cui alcuni dei valori sono vuoti. Alla fine della creazione di questi elenchi, desidero rimuovere questi elenchi vuoti prima di restituire il mio dizionario. Attualmente sto provando a fare questo come segue:

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

tuttavia, questo mi sta dando l'errore di runtime. Sono consapevole che non è possibile aggiungere / rimuovere elementi in un dizionario mentre si scorre attraverso di esso ... quale sarebbe un modo per aggirare questo?

Risposte:


455

In Python 2.x la chiamata keyscrea una copia della chiave su cui è possibile scorrere durante la modifica di dict:

for i in d.keys():

Nota che questo non funziona in Python 3.x perché keysrestituisce un iteratore invece di un elenco.

Un altro modo è usare listper forzare una copia delle chiavi da realizzare. Questo funziona anche in Python 3.x:

for i in list(d):

1
Credo che intendevi "chiamare keyscrea una copia delle chiavi su cui puoi scorrere", ovvero le pluralchiavi, giusto? Altrimenti come si può iterare su una singola chiave? A proposito, non sto prendendo in
giro

6
O tupla invece dell'elenco poiché è più veloce.
Brambor,

8
Per chiarire il comportamento di Python 3.x, d.keys () restituisce un iterabile (non un iteratore), il che significa che è una vista direttamente sulle chiavi del dizionario. L'uso in for i in d.keys()realtà funziona in Python 3.x in generale , ma poiché sta iterando su una vista iterabile delle chiavi del dizionario, la chiamata d.pop()durante il ciclo porta allo stesso errore che hai trovato. for i in list(d)emula il comportamento leggermente inefficiente di Python 2 di copiare le chiavi in ​​un elenco prima di iterare, per circostanze speciali come la tua.
Michael Krebs,

la soluzione python3 non funziona quando si desidera eliminare un oggetto in dict interno. per esempio hai un dic A e un dict B in dict A. se vuoi eliminare un oggetto in dict B si verifica un errore
Ali-T

1
In python3.x, list(d.keys())crea lo stesso output di list(d), chiamando lista dictrestituisce le chiavi. La keyschiamata (anche se non così costosa) non è necessaria.
Sean Breckenridge

46

Basta usare la comprensione del dizionario per copiare gli elementi rilevanti in un nuovo dict

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

Per questo 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]}

11
d.iteritems()mi ha dato un errore. Ho usato d.items()invece - usando python3
wcyn il

4
Questo funziona per il problema posto nella domanda di OP. Tuttavia, chiunque sia venuto qui dopo aver colpito questo RuntimeError in codice multi-thread, sii consapevole che il GIL di CPython può essere rilasciato anche nel mezzo della comprensione dell'elenco e devi risolverlo in modo diverso.
Yirkha,

40

Devi solo usare "copia":

In questo modo puoi scorrere i campi del dizionario originale e al volo puoi cambiare il dict desiderato (d dict). Funziona su ogni versione di Python, quindi è più chiaro.

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

Lavorato! Grazie.
Guilherme Carvalho Lithg,

13

Vorrei provare a evitare di inserire liste vuote in primo luogo, ma, in genere, utilizzerei:

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

Se precedente alla 2.7:

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

o semplicemente:

empty_key_vals = list(k for k in k,v in d.iteritems() if v)
for k in empty_key_vals:
    del[k]

+1: l'ultima opzione è interessante perché copia solo le chiavi di quegli elementi che devono essere eliminati. Ciò può fornire prestazioni migliori se è necessario eliminare solo un numero limitato di elementi rispetto alla dimensione del dict.
Mark Byers,

@MarkByers yup - e se un numero elevato lo fa, ricollegare il dict a uno nuovo filtrato è un'opzione migliore. È sempre l'aspettativa di come dovrebbe funzionare la struttura
Jon Clements

4
Un pericolo con il rebinding è che se da qualche parte nel programma c'era un oggetto che conteneva un riferimento al vecchio dict non vedeva i cambiamenti. Se sei certo che non è così, allora certo ... è un approccio ragionevole, ma è importante capire che non è esattamente lo stesso che modificare il dict originale.
Mark Byers,

@MarkByers punto estremamente positivo - Io e te lo sappiamo (e innumerevoli altri), ma non è ovvio per tutti. E metterò i soldi sul tavolo che non ti ha nemmeno morso nella parte posteriore :)
Jon Clements

Il punto di evitare di inserire le voci vuote è molto buono.
Magnus Bodin il

12

Per Python 3:

{k:v for k,v in d.items() if v}

Bello e conciso. Ha funzionato anche per me in Python 2.7.
donrondadon,

6

Non è possibile scorrere un dizionario mentre cambia durante il ciclo for. Crea un casting per elencarlo e scorrere su quell'elenco, funziona per me.

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

1

Per situazioni come questa, mi piace fare una copia profonda e scorrere quella copia mentre modifica il dict originale.

Se il campo di ricerca è all'interno di un elenco, è possibile enumerare il ciclo for dell'elenco e quindi specificare la posizione come indice per accedere al campo nel dict originale.


1

Questo ha funzionato per me:

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

Il cast degli elementi del dizionario da elencare crea un elenco dei suoi elementi, in modo da poter iterare su di esso ed evitare il RuntimeError.


1

dictc = {"stName": "asas"} keys = dictc.keys () per la chiave nella lista (chiavi): dictc [key.upper ()] = 'Nuovo valore' stampa (str (dictc))


0

Il motivo dell'errore di runtime è che non è possibile eseguire l'iterazione attraverso una struttura di dati mentre la struttura cambia durante l'iterazione.

Un modo per ottenere ciò che stai cercando è utilizzare l'elenco per aggiungere le chiavi che desideri rimuovere e quindi utilizzare la funzione pop sul dizionario per rimuovere la chiave identificata durante l'iterazione dell'elenco.

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)

0

Python 3 non consente l'eliminazione durante l'iterazione del dizionario (usando per il ciclo sopra). Ci sono varie alternative da fare; un modo semplice è quello di cambiare la seguente riga

for i in x.keys():

Con

for i in list(x)
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.