web-dev-qa-db-fra.com

Comment supprimer des éléments d'un dictionnaire en effectuant une itération dessus?

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?

258
user248237

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}
280
Blair

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)
82
Jochen Ritzel

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:

  • Copiez toutes les clés (ou les valeurs, ou les deux, selon vos besoins), puis parcourez-les. Vous pouvez utiliser .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.
  • Itérez sur 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.
18
user395760

Itérez sur une copie à la place, telle que celle renvoyée par items():

for k, v in list(mydict.items()):
17

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.

8
glihm

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.

8
rsanden

Vous pouvez utiliser une compréhension du dictionnaire.

d = {k:d[k] for k in d if d[k] != val}

5
Aaron

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]
4
Pob

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.

3
Michal Charemza

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])
1
JLandbrug