Disons que nous avons un dictionnaire Python d
, et nous l'itérons comme suit:
for k,v in d.iteritems():
del d[f(k)] # remove some item
d[g(k)] = v # add a new item
( f
et ne g
sont que quelques transformations de boîte noire.)
En d'autres termes, nous essayons d'ajouter / supprimer des éléments d
tout en l'itérant en utilisant iteritems
.
Est-ce bien défini? Pourriez-vous fournir quelques références pour étayer votre réponse?
(Il est assez évident de résoudre ce problème s'il est cassé, donc ce n'est pas l'angle que je recherche.)
Réponses:
Il est explicitement mentionné sur la page de documentation Python (pour Python 2.7 ) que
De même pour Python 3 .
De même pour
iter(d)
,d.iterkeys()
etd.itervalues()
, et je vais aller aussi loin que de dire que le fait pourfor k, v in d.items():
(je ne me souviens pas exactement ce quefor
fait, mais je ne serais pas surpris si la mise en œuvre appeléeiter(d)
).la source
d.items()
devrait être en sécurité dans Python 2.7 (le jeu change avec Python 3), car il crée ce qui est essentiellement une copie ded
, donc vous ne modifiez pas ce que vous itérez.viewitems()
Alex Martelli y réfléchit ici .
Il peut ne pas être prudent de changer le conteneur (par exemple, dict) en boucle sur le conteneur. Alors
del d[f(k)]
peut-être pas sûr. Comme vous le savez, la solution de contournement consiste à utiliserd.items()
(pour boucler sur une copie indépendante du conteneur) au lieu ded.iteritems()
(qui utilise le même conteneur sous-jacent).Il est normal de modifier la valeur à un index existant du dict, mais l'insertion de valeurs à de nouveaux index (par exemple
d[g(k)]=v
) peut ne pas fonctionner.la source
Vous ne pouvez pas faire cela, du moins avec
d.iteritems()
. Je l'ai essayé, et Python échoue avecSi vous utilisez à la place
d.items()
, cela fonctionne.En Python 3,
d.items()
est une vue dans le dictionnaire, commed.iteritems()
en Python 2. Pour faire cela en Python 3, utilisez plutôtd.copy().items()
. Cela nous permettra également de parcourir une copie du dictionnaire afin d'éviter de modifier la structure de données sur laquelle nous itérons.la source
2to3
) de Py2d.items()
en Py3 estlist(d.items())
, bien qu'elled.copy().items()
soit probablement d'une efficacité comparable.J'ai un grand dictionnaire contenant des tableaux Numpy, donc la chose dict.copy (). Keys () suggérée par @ murgatroid99 n'était pas faisable (même si cela fonctionnait). Au lieu de cela, je viens de convertir le keys_view en une liste et cela a bien fonctionné (en Python 3.4):
for item in list(dict_d.keys()): temp = dict_d.pop(item) dict_d['some_key'] = 1 # Some value
Je me rends compte que cela ne plonge pas dans le domaine philosophique du fonctionnement interne de Python comme les réponses ci-dessus, mais cela fournit une solution pratique au problème déclaré.
la source
Le code suivant montre que cela n'est pas bien défini:
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
Le premier exemple appelle g (k) et lève une exception (le dictionnaire a changé de taille pendant l'itération).
Le deuxième exemple appelle h (k) et ne lève aucune exception, mais renvoie:
{21: 'axx', 22: 'bxx', 23: 'cxx'}
Ce qui, en regardant le code, semble faux - je me serais attendu à quelque chose comme:
{11: 'ax', 12: 'bx', 13: 'cx'}
la source
{11: 'ax', 12: 'bx', 13: 'cx'}
mais les 21,22,23 devraient vous donner un indice sur ce qui s'est réellement passé: votre boucle a traversé les éléments 1, 2, 3, 11, 12, 13 mais n'a pas réussi à récupérer le second ronde de nouveaux éléments au fur et à mesure qu'ils ont été insérés devant les éléments que vous aviez déjà itérés. Changezh()
pour revenirx+5
et vous obtenez un autre x:'axxx'
etc. ou 'x + 3' et vous obtenez le magnifique'axxxxx'
{11: 'ax', 12: 'bx', 13: 'cx'}
comme vous l'avez dit, donc je vais mettre à jour mon article à ce sujet. Quoi qu'il en soit, ce comportement n'est clairement pas bien défini.J'ai eu le même problème et j'ai utilisé la procédure suivante pour résoudre ce problème.
La liste Python peut être itérée même si vous modifiez lors de l'itération dessus. donc pour le code suivant, il imprimera les 1 à l'infini.
for i in list: list.append(1) print 1
Donc, en utilisant list et dict en collaboration, vous pouvez résoudre ce problème.
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))
la source
Python 3, vous devriez simplement:
prefix = 'item_' t = {'f1': 'ffw', 'f2': 'fca'} t2 = dict() for k,v in t.items(): t2[k] = prefix + v
Ou utiliser:
Vous ne devez jamais modifier le dictionnaire original, cela entraîne de la confusion ainsi que des bogues potentiels ou des RunTimeErrors. Sauf si vous ajoutez simplement au dictionnaire de nouveaux noms de clé.
la source
Aujourd'hui, j'avais un cas d'utilisation similaire, mais au lieu de simplement matérialiser les clés du dictionnaire au début de la boucle, je voulais que les modifications du dict affectent l'itération du dict, qui était un dict ordonné.
J'ai fini par créer la routine suivante, qui peut également être trouvée dans 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 docstring illustre l'utilisation. Cette fonction pourrait être utilisée à la place de
d.iteritems()
ci - dessus pour avoir l'effet souhaité.la source