Est-il légitime de supprimer des éléments d'un dictionnaire dans Python tout en effectuant une itération dessus?
Par exemple:
for k, v in mydict.iteritems():
if k == val:
del mydict[k]
L'idée est de supprimer du dictionnaire les éléments qui ne remplissent pas une condition donnée, au lieu de créer un nouveau dictionnaire qui est un sous-ensemble de celui sur lequel on effectue une itération.
Est-ce une bonne solution? Y a-t-il des moyens plus élégants/efficaces?
EDIT:
Cette réponse ne fonctionnera pas pour Python3 et donnera un RuntimeError
.
RuntimeError: la taille du dictionnaire a été modifiée pendant l'itération.
Cela se produit parce que mydict.keys()
renvoie un itérateur, pas une liste. Comme indiqué dans les commentaires, convertissez simplement mydict.keys()
en liste par list(mydict.keys())
et cela devrait fonctionner.
Un simple test dans la console montre que vous ne pouvez pas modifier un dictionnaire en le parcourant:
>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k, v in mydict.iteritems():
... if k == 'two':
... del mydict[k]
...
------------------------------------------------------------
Traceback (most recent call last):
File "<ipython console>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
Comme indiqué dans la réponse de delnan, la suppression d'entrées pose des problèmes lorsque l'itérateur essaie de passer à l'entrée suivante. À la place, utilisez la méthode keys()
pour obtenir une liste des clés et utilisez-la:
>>> for k in mydict.keys():
... if k == 'two':
... del mydict[k]
...
>>> mydict
{'four': 4, 'three': 3, 'one': 1}
Si vous devez supprimer en fonction de la valeur des éléments, utilisez plutôt la méthode items()
:
>>> for k, v in mydict.items():
... if v == 3:
... del mydict[k]
...
>>> mydict
{'four': 4, 'one': 1}
Vous pouvez également le faire en deux étapes:
remove = [k for k in mydict if k == val]
for k in remove: del mydict[k]
Mon approche préférée est généralement de faire un nouveau dict:
# Python 2.7 and 3.x
mydict = { k:v for k,v in mydict.items() if k!=val }
# before Python 2.7
mydict = dict((k,v) for k,v in mydict.iteritems() if k!=val)
Vous ne pouvez pas modifier une collection en l'itérant. C'est là que réside la folie - notamment si vous étiez autorisé à supprimer l'élément en cours, l'itérateur devrait passer à (+1) et le prochain appel à next
vous mènerait au-delà de celui-ci (+2). , de sorte que vous finissiez par sauter un élément (celui juste derrière celui que vous avez supprimé). Vous avez deux options:
.keys()
et al pour cela (dans Python 3, passez l'itérateur résultant à list
). Cela pourrait être très inutile en termes d'espace.mydict
comme d'habitude, en enregistrant les clés à supprimer dans une collection séparée to_delete
. Lorsque vous avez terminé l'itération mydict
, supprimez tous les éléments de to_delete
de mydict
. Enregistre une partie de l'espace (en fonction du nombre de clés supprimées et du nombre de touches restantes) par rapport à la première approche, mais nécessite également quelques lignes supplémentaires.Itérez sur une copie à la place, telle que celle renvoyée par items()
:
for k, v in list(mydict.items()):
Avec python3, itérer sur dic.keys () soulèvera l’erreur de taille du dictionnaire. Vous pouvez utiliser cette autre manière:
Testé avec python3, cela fonctionne bien et l'erreur "la taille du dictionnaire a été modifiée au cours de l'itération" n'est pas générée:
my_dic = { 1:10, 2:20, 3:30 }
# Is important here to cast because ".keys()" method returns a dict_keys object.
key_list = list( my_dic.keys() )
# Iterate on the list:
for k in key_list:
print(key_list)
print(my_dic)
del( my_dic[k] )
print( my_dic )
# {}
Je l'utilise quand, à partir d'un dictionnaire utilisant beaucoup de mémoire, je veux construire un autre dictionnaire (contenant la modification du premier) sans faire de "copie" et surcharger la RAM.
C'est plus propre à utiliser list(mydict)
:
>>> mydict = {'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> for k in list(mydict):
... if k == 'three':
... del mydict[k]
...
>>> mydict
{'four': 4, 'two': 2, 'one': 1}
Ceci correspond à une structure parallèle pour les listes:
>>> mylist = ['one', 'two', 'three', 'four']
>>> for k in list(mylist): # or mylist[:]
... if k == 'three':
... mylist.remove(k)
...
>>> mylist
['one', 'two', 'four']
Les deux fonctionnent en python2 et python3.
Vous pouvez utiliser une compréhension du dictionnaire.
d = {k:d[k] for k in d if d[k] != val}
Vous pouvez d’abord créer une liste de clés à supprimer, puis parcourir cette liste pour les supprimer.
dict = {'one' : 1, 'two' : 2, 'three' : 3, 'four' : 4}
delete = []
for k,v in dict.items():
if v%2 == 1:
delete.append(k)
for i in delete:
del dict[i]
Il y a un moyen qui peut convenir si les éléments que vous voulez supprimer sont toujours au "début" de l'itération dict
while mydict:
key, value = next(iter(mydict.items()))
if should_delete(key, value):
del mydict[key]
else:
break
La cohérence du "début" n’est garantie que pour certaines versions/implémentations Python. Par exemple, de Quoi de neuf dans Python 3.7
la nature de préservation d'ordre d'ordre d'insertion des objets dict a été déclarée être une partie officielle de la spécification de langage Python.
Cela évite une copie du dictée suggérée par de nombreuses autres réponses, du moins dans Python 3.
J'ai essayé les solutions ci-dessus en Python3, mais celle-ci semble être la seule qui fonctionne pour moi lors du stockage d'objets dans un dict. Fondamentalement, vous copiez votre dict () et parcourez-le en supprimant les entrées de votre dictionnaire d'origine.
tmpDict = realDict.copy()
for key, value in tmpDict.items():
if value:
del(realDict[key])