¿Cómo evitar el error "RuntimeError: el diccionario cambió de tamaño durante la iteración"?

258

He revisado todas las otras preguntas con el mismo error y aún no he encontrado una solución útil = /

Tengo un diccionario de listas:

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

en el que algunos de los valores están vacíos. Al final de crear estas listas, quiero eliminar estas listas vacías antes de devolver mi diccionario. Actual Estoy intentando hacer esto de la siguiente manera:

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

Sin embargo, esto me está dando el error de tiempo de ejecución. Soy consciente de que no se pueden agregar / eliminar elementos en un diccionario mientras se itera a través de él ... ¿cuál sería una forma de evitar esto entonces?

usuario1530318
fuente

Respuestas:

455

En Python 2.x, la llamada keyshace una copia de la clave sobre la que puede iterar mientras modifica dict:

for i in d.keys():

Tenga en cuenta que esto no funciona en Python 3.x porque keysdevuelve un iterador en lugar de una lista.

Otra forma es usar listpara forzar una copia de las claves a realizar. Este también funciona en Python 3.x:

for i in list(d):
Mark Byers
fuente
1
Creo que quiso decir 'llamar keyshace una copia de las claves que puede iterar sobre' también conocido como las pluralclaves ¿verdad? De lo contrario, ¿cómo se puede iterar sobre una sola tecla? Por cierto, no estoy
buscando
66
O tupla en lugar de lista, ya que es más rápido.
Brambor
8
Para aclarar el comportamiento de python 3.x, d.keys () devuelve un iterable (no un iterador), lo que significa que es una vista de las teclas del diccionario directamente. El uso for i in d.keys()realmente funciona en python 3.x en general , pero debido a que está iterando sobre una vista iterable de las teclas del diccionario, llamar d.pop()durante el ciclo conduce al mismo error que encontró. for i in list(d)emula el comportamiento un poco ineficiente de python 2 de copiar las claves a una lista antes de iterar, para circunstancias especiales como la suya.
Michael Krebs el
su solución de python3 no funciona cuando desea eliminar un objeto en dict interno. por ejemplo, tiene un dic A y un dict B en el dict A. si desea eliminar un objeto en el dict B, se produce un error
Ali-T
1
En python3.x, list(d.keys())crea la misma salida que list(d), al llamar lista dictdevuelve las claves. La keysllamada (aunque no es tan cara) es innecesaria.
Sean Breckenridge
46

Simplemente use la comprensión del diccionario para copiar los elementos relevantes en un nuevo 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]}

Para esto en 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
fuente
11
d.iteritems()me dio un error Solía d.items()lugar - utilizando python3
wcyn
44
Esto funciona para el problema planteado en la pregunta de OP. Sin embargo, cualquiera que haya venido aquí después de golpear este RuntimeError en código multiproceso, tenga en cuenta que GIL de CPython también puede liberarse en el medio de la comprensión de la lista y debe solucionarlo de manera diferente.
Yirkha
40

Solo necesita usar "copiar":

De esa manera, usted itera sobre los campos del diccionario original y sobre la marcha puede cambiar el dict deseado (d dict). Funciona en cada versión de Python, por lo que es más claro.

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
fuente
¡Trabajó! Gracias.
Guilherme Carvalho Lithg
13

Intentaría evitar insertar listas vacías en primer lugar, pero, en general, usaría:

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

Si es anterior a 2.7:

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

o solo:

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
fuente
+1: la última opción es interesante porque solo copia las claves de aquellos elementos que necesitan eliminarse. Esto puede proporcionar un mejor rendimiento si solo se necesita eliminar un pequeño número de elementos en relación con el tamaño del dict.
Mark Byers
@MarkByers, sí, y si un gran número lo hace, volver a vincular el dict a uno nuevo que se filtre es una mejor opción. Siempre es la expectativa de cómo debería funcionar la estructura
Jon Clements
44
Un peligro con volver a vincular es si en algún lugar del programa había un objeto que tenía una referencia al antiguo dict que no vería los cambios. Si está seguro de que ese no es el caso, entonces seguro ... ese es un enfoque razonable, pero es importante comprender que no es lo mismo que modificar el dict original.
Mark Byers
@MarkByers punto extremadamente bueno: usted y yo lo sabemos (y muchos otros), pero no es obvio para todos. Y pondré dinero sobre la mesa, no te ha mordido por la espalda :)
Jon Clements
El punto de evitar insertar las entradas vacías es muy bueno.
Magnus Bodin
12

Para Python 3:

{k:v for k,v in d.items() if v}
ucyo
fuente
Agradable y conciso. Trabajó para mí en Python 2.7 también.
donrondadon
6

No puede iterar a través de un diccionario mientras está cambiando durante el ciclo for. Hacer un casting para enumerar e iterar sobre esa lista, funciona para mí.

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

Para situaciones como esta, me gusta hacer una copia profunda y recorrer esa copia mientras modifico el dict original.

Si el campo de búsqueda está dentro de una lista, puede enumerar en el bucle for de la lista y luego especificar la posición como índice para acceder al campo en el dict original.

Ajayi Oluwaseun Emmanuel
fuente
1

Esto funcionó para mí:

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

La conversión de los elementos del diccionario a la lista crea una lista de sus elementos, para que pueda iterar sobre ella y evitar el RuntimeError.

Singrium
fuente
1

dictc = {"stName": "asas"} keys = dictc.keys () para la clave en la lista (keys): dictc [key.upper ()] = 'Nuevo valor' print (str (dictc))

vaibhav.patil
fuente
0

La razón del error de tiempo de ejecución es que no puede iterar a través de una estructura de datos mientras su estructura cambia durante la iteración.

Una forma de lograr lo que está buscando es usar la lista para agregar las claves que desea eliminar y luego usar la función pop en el diccionario para eliminar la clave identificada mientras recorre la lista.

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

Python 3 no permite la eliminación al iterar (usando para el bucle anterior) diccionario. Hay varias alternativas para hacer; una forma simple es cambiar la siguiente línea

for i in x.keys():

Con

for i in list(x)
Hasham
fuente