Modificando un dict de Python mientras lo iteramos

Digamos que tenemos un diccionario de Python d , y lo estamos repitiendo así:

 for k,v in d.iteritems(): del d[f(k)] # remove some item d[g(k)] = v # add a new item 

( f y g son solo algunas transformaciones de caja negra).

En otras palabras, intentamos agregar / eliminar elementos a d mientras iteramos sobre él usando iteritems .

¿Está bien definido? ¿Podría proporcionar algunas referencias para apoyar su respuesta?

(Es bastante obvio cómo solucionar esto si está roto, por lo que este no es el ángulo que busco).

Se menciona explícitamente en la página de documentos de Python (para Python 2.7 ) que

El uso de iteritems() al agregar o eliminar entradas en el diccionario puede generar un RuntimeError o fallar en iterar sobre todas las entradas.

Del mismo modo para Python 3 .

Lo mismo se aplica a iter(d) , d.iterkeys() y d.itervalues() , e iré tan lejos como para decir que lo hace for k, v in d.items(): (No puedo recuerde exactamente for qué sirve, pero no me sorprendería si la implementación se llamara iter(d) ).

Alex Martelli pesa en esto aquí .

Puede que no sea seguro cambiar el contenedor (por ejemplo, dict) mientras se realiza un bucle sobre el contenedor. Así que del d[f(k)] puede no ser seguro. Como saben, la solución es usar d.items() (para hacer un bucle sobre una copia independiente del contenedor) en lugar de d.iteritems() (que usa el mismo contenedor subyacente).

Está bien modificar el valor en un índice existente del dict, pero la inserción de valores en nuevos índices (por ejemplo, d[g(k)]=v ) puede no funcionar.

No puedes hacer eso, al menos con d.iteritems() . Lo probé, y Python falla con

 RuntimeError: dictionary changed size during iteration 

Si en su lugar usa d.items() , entonces funciona.

En Python 3, d.items() es una vista del diccionario, como d.iteritems() en Python 2. Para hacer esto en Python 3, use d.copy().items() . De manera similar, esto nos permitirá iterar sobre una copia del diccionario para evitar modificar la estructura de datos sobre la que estamos iterando.

El siguiente código muestra que esto no está bien definido:

 def f(x): return x def g(x): return x+1 def h(x): return x+10 try: d = {1:"a", 2:"b", 3:"c"} for k, v in d.iteritems(): del d[f(k)] d[g(k)] = v+"x" print d except Exception as e: print "Exception:", e try: d = {1:"a", 2:"b", 3:"c"} for k, v in d.iteritems(): del d[f(k)] d[h(k)] = v+"x" print d except Exception as e: print "Exception:", e 

El primer ejemplo llama a g (k) y lanza una excepción (el tamaño del diccionario cambió durante la iteración).

El segundo ejemplo llama a h (k) y no genera ninguna excepción, pero genera:

 {21: 'axx', 22: 'bxx', 23: 'cxx'} 

Lo cual, al mirar el código, parece incorrecto, habría esperado algo como:

 {11: 'ax', 12: 'bx', 13: 'cx'} 

Tengo un gran diccionario que contiene matrices de Numpy, por lo que la cosa dict.copy (). Keys () sugerida por @murgatroid99 no era factible (aunque funcionó). En su lugar, simplemente convertí keys_view a una lista y funcionó bien (en Python 3.4):

 for item in list(dict_d.keys()): temp = dict_d.pop(item) dict_d['some_key'] = 1 # Some value 

Me doy cuenta de que esto no se adentra en el ámbito filosófico del funcionamiento interno de Python, como las respuestas anteriores, pero proporciona una solución práctica al problema planteado.

Tuve el mismo problema y utilicé el siguiente procedimiento para resolver este problema.

La Lista de Python puede iterar incluso si se modifica durante la iteración sobre ella. así que para el siguiente código se imprimirá 1 infinitamente.

 for i in list: list.append(1) print 1 

Así que usando list y dictado en colaboración puedes resolver este problema.

 d_list=[] d_dict = {} for k in d_list: if d_dict[k] is not -1: d_dict[f(k)] = -1 # rather than deleting it mark it with -1 or other value to specify that it will be not considered further(deleted) d_dict[g(k)] = v # add a new item d_list.append(g(k)) 

Hoy tuve un caso de uso similar, pero en lugar de materializar simplemente las claves en el diccionario al comienzo del bucle, quería que los cambios en el dict para afectar la iteración del dict, que era un dictado ordenado.

Terminé construyendo la siguiente rutina, que también se puede encontrar en jaraco.itertools :

 def _mutable_iter(dict): """ Iterate over items in the dict, yielding the first one, but allowing it to be mutated during the process. >>> d = dict(a=1) >>> it = _mutable_iter(d) >>> next(it) ('a', 1) >>> d {} >>> d.update(b=2) >>> list(it) [('b', 2)] """ while dict: prev_key = next(iter(dict)) yield prev_key, dict.pop(prev_key) 

La cadena de documentos ilustra el uso. Esta función se podría utilizar en lugar de d.iteritems() para tener el efecto deseado.

Python 3 debes simplemente:

 prefix = 'item_' t = {'f1': 'ffw', 'f2': 'fca'} t2 = dict() for k,v in t.items(): t2[k] = prefix + v 

o usar:

 t2 = t1.copy() 

Nunca debe modificar el diccionario original, puede generar confusión, así como posibles errores o RunTimeErrors. A menos que solo agregue al diccionario con nuevos nombres de clave.