Comment éviter l'erreur «RuntimeError: le dictionnaire a changé de taille pendant l'itération»?

258

J'ai vérifié toutes les autres questions avec la même erreur mais je n'ai trouvé aucune solution utile = /

J'ai un dictionnaire de listes:

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

dans lequel certaines des valeurs sont vides. À la fin de la création de ces listes, je souhaite supprimer ces listes vides avant de retourner mon dictionnaire. Actuellement, j'essaie de le faire comme suit:

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

cependant, cela me donne l'erreur d'exécution. Je suis conscient que vous ne pouvez pas ajouter / supprimer des éléments dans un dictionnaire tout en le parcourant ... quel serait alors un moyen de contourner cela?

user1530318
la source

Réponses:

455

Dans Python 2.x, l'appel keysfait une copie de la clé que vous pouvez parcourir tout en modifiant dict:

for i in d.keys():

Notez que cela ne fonctionne pas dans Python 3.x car keysretourne un itérateur au lieu d'une liste.

Une autre façon consiste à utiliser listpour forcer une copie des clés à effectuer. Celui-ci fonctionne également en Python 3.x:

for i in list(d):
Mark Byers
la source
1
Je pense que vous vouliez dire «appeler keysfait une copie des clés que vous pouvez parcourir» alias les pluralclés, n'est-ce pas? Sinon, comment peut-on répéter sur une seule clé? Soit dit en passant, je ne suis pas en train de choisir, je suis vraiment intéressé de savoir si c'est vraiment la ou les clés
HighOnMeat
6
Ou tuple au lieu de liste car c'est plus rapide.
Brambor
8
Pour clarifier le comportement de python 3.x, d.keys () retourne un itérable (pas un itérateur) ce qui signifie que c'est une vue directement sur les clés du dictionnaire. L'utilisation for i in d.keys()fonctionne en fait dans python 3.x en général , mais parce qu'il itère sur une vue itérable des clés du dictionnaire, appeler d.pop()pendant la boucle conduit à la même erreur que vous avez trouvée. for i in list(d)émule le comportement légèrement inefficace de python 2 consistant à copier les clés dans une liste avant l'itération, dans des circonstances spéciales comme la vôtre.
Michael Krebs
votre solution python3 ne fonctionne pas lorsque vous souhaitez supprimer un objet dans dict interne. par exemple, vous avez un dic A et un dict B dans le dict A. Si vous voulez supprimer un objet dans le dict B, il se produit une erreur
Ali-T
1
Dans python3.x, list(d.keys())crée la même sortie que list(d), l'appel listà a dictrenvoie les clés. L' keysappel (mais pas si cher) n'est pas nécessaire.
Sean Breckenridge
46

Utilisez simplement la compréhension du dictionnaire pour copier les éléments pertinents dans un nouveau 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]}

Pour cela 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
la source
11
d.iteritems()m'a donné une erreur. J'ai utilisé à la d.items()place - en utilisant python3
wcyn
4
Cela fonctionne pour le problème posé dans la question de OP. Cependant, quiconque est venu ici après avoir frappé cette RuntimeError en code multi-thread, sachez que le GIL de CPython peut également être publié au milieu de la compréhension de la liste et vous devez le corriger différemment.
Yirkha
40

Il vous suffit d'utiliser "copier":

De cette façon, vous parcourez les champs du dictionnaire d'origine et à la volée, vous pouvez changer le dict souhaité (d dict). Cela fonctionne sur chaque version de python, donc c'est plus clair.

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
la source
Travaillé! Je vous remercie.
Guilherme Carvalho Lithg
13

J'essaierais d'éviter d'insérer des listes vides en premier lieu, mais j'utiliserais généralement:

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

Si avant 2.7:

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

ou juste:

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
la source
+1: la dernière option est intéressante car elle ne copie que les clés des éléments à supprimer. Cela peut donner de meilleures performances si seul un petit nombre d'éléments doivent être supprimés par rapport à la taille du dict.
Mark Byers
@MarkByers yup - et si un grand nombre le fait, alors relier le dict à un nouveau filtré est une meilleure option. C'est toujours l'attente du fonctionnement de la structure
Jon Clements
4
Un danger avec la reliure est que si quelque part dans le programme il y avait un objet qui contenait une référence à l'ancien dict, il ne verrait pas les changements. Si vous êtes certain que ce n'est pas le cas, alors bien sûr ... c'est une approche raisonnable, mais il est important de comprendre que ce n'est pas tout à fait la même chose que de modifier le dict original.
Mark Byers
@MarkByers extrêmement bon point - Vous et moi le savons (et d'innombrables autres), mais ce n'est pas évident pour tous. Et je mettrai de l'argent sur la table ça ne vous a pas mordu à l'arrière :)
Jon Clements
Le point d'éviter d'insérer les entrées vides est très bon.
Magnus Bodin
12

Pour Python 3:

{k:v for k,v in d.items() if v}
ucyo
la source
Agréable et concis. A également fonctionné pour moi en Python 2.7.
donrondadon
6

Vous ne pouvez pas parcourir un dictionnaire pendant qu'il change pendant la boucle for. Faire un casting pour lister et itérer sur cette liste, ça marche pour moi.

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

Pour des situations comme celle-ci, j'aime faire une copie complète et boucler cette copie tout en modifiant le dict original.

Si le champ de recherche se trouve dans une liste, vous pouvez énumérer dans la boucle for de la liste, puis spécifier la position comme index pour accéder au champ dans le dict d'origine.

Ajayi Oluwaseun Emmanuel
la source
1

Cela a fonctionné pour moi:

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 conversion des éléments du dictionnaire en liste crée une liste de ses éléments, de sorte que vous pouvez itérer dessus et éviter le RuntimeError.

singrium
la source
1

dictc = {"stName": "asas"} keys = dictc.keys () pour la clé dans la liste (keys): dictc [key.upper ()] = 'New value' print (str (dictc))

vaibhav.patil
la source
0

La raison de l'erreur d'exécution est que vous ne pouvez pas parcourir une structure de données pendant que sa structure change pendant l'itération.

Une façon d'atteindre ce que vous recherchez consiste à utiliser la liste pour ajouter les clés que vous souhaitez supprimer, puis à utiliser la fonction pop sur le dictionnaire pour supprimer la clé identifiée tout en parcourant la liste.

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
la source
0

Python 3 n'autorise pas la suppression lors de l'itération (à l'aide de la boucle ci-dessus) du dictionnaire. Il existe différentes alternatives à faire; une façon simple est de changer la ligne suivante

for i in x.keys():

Avec

for i in list(x)
Hasham
la source