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 d
mientras iteramos sobre él usando iteritems
.
¿Está esto bien definido? ¿Podría proporcionar algunas referencias para respaldar su respuesta?
(Es bastante obvio cómo arreglar esto si está roto, por lo que este no es el ángulo que busco).
python
dictionary
NPE
fuente
fuente
Respuestas:
Se menciona explícitamente en la página del documento de Python (para Python 2.7 ) que
De manera similar para Python 3 .
Lo mismo vale para
iter(d)
,d.iterkeys()
yd.itervalues()
, iré tan lejos como para decir que lo hacefor k, v in d.items():
(no recuerdo exactamente quéfor
es lo que hace, pero no me sorprendería si la implementación llamaraiter(d)
).fuente
d.items()
debería ser seguro en Python 2.7 (el juego cambia con Python 3), ya que hace lo que es esencialmente una copia ded
, por lo que no está modificando lo que está iterando.viewitems()
Alex Martelli interviene en esto aquí .
Puede que no sea seguro cambiar el contenedor (por ejemplo, dict) mientras se coloca sobre el contenedor. Así que
del d[f(k)]
puede que no sea seguro. Como sabe, la solución es usard.items()
(para recorrer una copia independiente del contenedor) en lugar ded.iteritems()
(que usa el mismo contenedor subyacente).Está bien modificar el valor en un índice existente del dict, pero es posible que la inserción de valores en índices nuevos (por ejemplo
d[g(k)]=v
) no funcione.fuente
No puedes hacer eso, al menos con
d.iteritems()
. Lo probé y Python falla conSi en cambio lo usa
d.items()
, entonces funciona.En Python 3,
d.items()
es una vista del diccionario, comod.iteritems()
en Python 2. Para hacer esto en Python 3, en su lugar used.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.fuente
2to3
) de Py2d.items()
a Py3 eslist(d.items())
, aunqued.copy().items()
probablemente sea de eficiencia comparable.Tengo un diccionario grande que contiene matrices Numpy, por lo que lo dict.copy (). Keys () sugerido por @ murgatroid99 no fue factible (aunque funcionó). En cambio, acabo de convertir keys_view en 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 sumerge en el ámbito filosófico del funcionamiento interno de Python como las respuestas anteriores, pero proporciona una solución práctica al problema planteado.
fuente
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 arroja una excepción (el tamaño del diccionario cambió durante la iteración).
El segundo ejemplo llama a h (k) y no arroja ninguna excepción, pero genera:
{21: 'axx', 22: 'bxx', 23: 'cxx'}
Lo cual, mirando el código, parece incorrecto, habría esperado algo como:
{11: 'ax', 12: 'bx', 13: 'cx'}
fuente
{11: 'ax', 12: 'bx', 13: 'cx'}
pero el 21,22,23 debería darle una pista de lo que realmente sucedió: su ciclo pasó por los elementos 1, 2, 3, 11, 12, 13 pero no logró recoger el segundo ronda de elementos nuevos a medida que se insertaron delante de los elementos que ya había iterado. Cambieh()
para regresarx+5
y obtendrá otra x:'axxx'
etc. o 'x + 3' y obtendrá el magnífico'axxxxx'
{11: 'ax', 12: 'bx', 13: 'cx'}
que dijiste, así que actualizaré mi publicación al respecto. De cualquier manera, este claramente no es un comportamiento bien definido.Tengo el mismo problema y utilicé el siguiente procedimiento para resolver este problema.
La lista de Python se puede iterar incluso si la modifica durante la iteración sobre ella. así que para el siguiente código imprimirá 1 infinitamente.
for i in list: list.append(1) print 1
Entonces, usando list y dict de manera colaborativa, puede 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))
fuente
Python 3 solo debes:
prefix = 'item_' t = {'f1': 'ffw', 'f2': 'fca'} t2 = dict() for k,v in t.items(): t2[k] = prefix + v
o usar:
Nunca debe modificar el diccionario original, ya que genera confusión, así como posibles errores o RunTimeErrors. A menos que agregue al diccionario nuevos nombres de clave.
fuente
Hoy tuve un caso de uso similar, pero en lugar de simplemente materializar las claves en el diccionario al comienzo del ciclo, quería que los cambios en el dictado afectaran la iteración del dictado, 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 podría utilizarse en lugar de la
d.iteritems()
anterior para obtener el efecto deseado.fuente