Supprimer plusieurs clés d'un dictionnaire en toute sécurité

128

Je sais que pour supprimer une entrée, «clé» de mon dictionnaire d, en toute sécurité, vous faites:

if d.has_key('key'):
    del d['key']

Cependant, je dois supprimer plusieurs entrées du dictionnaire en toute sécurité. Je pensais définir les entrées dans un tuple car je devrai le faire plus d'une fois.

entitiesToREmove = ('a', 'b', 'c')
for x in entitiesToRemove:
    if d.has_key(x):
        del d[x]

Cependant, je me demandais s'il existe un moyen plus intelligent de le faire?

dublintech
la source
3
Le temps de récupération d'un dictionnaire est proche de O (1) en raison du hachage. À moins que vous ne supprimiez une part importante des entrées, je ne pense pas que vous ferez beaucoup mieux.
ncmathsadist
1
La réponse de @mattbornski semble plus canonique et aussi succincte.
Ioannis Filippidis
2
StackOverflow a parlé: key in dest plus pythonique que d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Michael Scheper
Si vous pouvez épargner un peu de mémoire, vous pouvez le faire for x in set(d) & entities_to_remove: del d[x]. Ce ne sera probablement plus efficace que s'il entities_to_removeest "grand".
DylanYoung

Réponses:

56

Pourquoi pas comme ça:

entries = ('a', 'b', 'c')
the_dict = {'b': 'foo'}

def entries_to_remove(entries, the_dict):
    for key in entries:
        if key in the_dict:
            del the_dict[key]

Une version plus compacte a été fournie par mattbornski en utilisant dict.pop ()

Glaslos
la source
14
Ajouter ceci pour les personnes provenant d'un moteur de recherche. Si les clés sont connues (lorsque la sécurité n'est pas un problème), plusieurs clés peuvent être supprimées sur une seule ligne comme celle-cidel dict['key1'], dict['key2'], dict['key3']
Tirtha R
Selon le nombre de clés que vous supprimez, il peut être plus efficace d'utiliser for key in set(the_dict) & entries:et de contourner le key in dicttest.
DylanYoung le
236
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)
Mattbornski
la source
38
Ce. C'est le choix intelligent de Pythonista. dict.pop()élimine le besoin de tests d'existence de clés. Excellent.
Cecil Curry
4
Pour ce que ça vaut, je pense que .pop()c'est mauvais et impythonique, et préférerais la réponse acceptée à celle-ci.
Arne
5
Un nombre impressionnant de personnes ne semble pas dérangé par cela :) Je ne me soucie pas de la ligne supplémentaire pour vérifier l'existence personnellement, et c'est beaucoup plus lisible à moins que vous ne connaissiez déjà pop (). D'un autre côté, si vous essayez de le faire dans une compréhension ou un lambda en ligne, cette astuce pourrait être d'une grande aide. Je dirais aussi qu'il est important, à mon avis, de rencontrer les gens là où ils sont. Je ne suis pas sûr que "mauvais et impythonique" va donner aux gens qui lisent ces réponses les conseils pratiques qu'ils recherchent.
mattbornski
5
Il y a une raison particulièrement bonne d'utiliser cela. Bien que l'ajout d'une ligne supplémentaire puisse améliorer la «lisibilité» ou la «clarté», il ajoute également une recherche supplémentaire dans le dictionnaire. Cette méthode est l'équivalent de la suppression de faire setdefault. S'il est mis en œuvre correctement (et je suis sûr que c'est le cas), il ne fait qu'une seule recherche dans la carte de hachage qui est le dict, au lieu de deux.
Mad Physicist
2
Personnellement, je me préoccuperais d'abord de l'exactitude et de la maintenabilité, et de la vitesse seulement s'il s'avère que la rapidité est insuffisante. La différence de vitesse entre ces opérations sera insignifiante lors d'un zoom arrière au niveau de l'application. Il se peut que l'on soit plus rapide, mais je m'attends à ce que dans le monde réel, vous ne le remarquiez ni ne vous en souciez, et si vous le remarquez et vous en souciez, vous serez mieux servi à réécrire dans quelque chose de plus performant que Python.
mattbornski
90

Utilisation de Dict Comprehensions

final_dict = {key: t[key] for key in t if key not in [key1, key2]}

key1 et key2 doivent être supprimés.

Dans l'exemple ci-dessous, les clés "b" et "c" doivent être supprimées et elles sont conservées dans une liste de clés.

>>> a
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
>>> keys = ["b", "c"]
>>> print {key: a[key] for key in a if key not in keys}
{'a': 1, 'd': 4}
>>> 
Abhijeet Rastogi
la source
4
nouveau dictionnaire? compréhension de la liste? Vous devez ajuster la réponse à la personne qui pose la question;)
Glaslos
6
Cette solution a un sérieux impact sur les performances lorsque la variable contenant le est utilisée ultérieurement dans le programme. En d'autres termes, un dict dont les clés ont été supprimées est beaucoup plus efficace qu'un dict nouvellement créé avec les éléments conservés.
Apalala
14
par souci de lisibilité, je suggère {k: v pour k, v dans t.items () si k n'est pas dans [key1, key2]}
Frederic Bazin
8
Cela pose également des problèmes de performances lorsque la liste de clés est trop longue, au fur et à mesure des recherches O(n). L'opération entière est O(mn), où mest le nombre de clés dans le dict et nle nombre de clés dans la liste. Je suggère d'utiliser un ensemble à la {key1, key2}place, si possible.
ldavid
4
À Apalala: pouvez-vous m'aider à comprendre pourquoi il y a un coup de performance?
Sean
21

une solution utilise mapet filterfonctionne

python 2

d={"a":1,"b":2,"c":3}
l=("a","b","d")
map(d.__delitem__, filter(d.__contains__,l))
print(d)

python 3

d={"a":1,"b":2,"c":3}
l=("a","b","d")
list(map(d.__delitem__, filter(d.__contains__,l)))
print(d)

vous obtenez:

{'c': 3}
José Ricardo Bustos M.
la source
Cela ne fonctionne pas pour moi avec python 3.4:>>> d={"a":1,"b":2,"c":3} >>> l=("a","b","d") >>> map(d.__delitem__, filter(d.__contains__,l)) <map object at 0x10579b9e8> >>> print(d) {'a': 1, 'b': 2, 'c': 3}
Risadinha
@Risadinha list(map(d.__delitem__,filter(d.__contains__,l))).... en python 3.4, la fonction de carte renvoie un itérateur
Jose Ricardo Bustos M.
4
ou deque(map(...), maxlen=0)pour éviter de construire une liste de valeurs None; première importation avecfrom collections import deque
Jason
19

Si vous aviez également besoin de récupérer les valeurs des clés que vous supprimez, ce serait un très bon moyen de le faire:

valuesRemoved = [d.pop(k, None) for k in entitiesToRemove]

Vous pouvez bien sûr toujours le faire juste pour supprimer les clés de d, mais vous créeriez inutilement la liste de valeurs avec la compréhension de liste. Il est également peu clair d'utiliser une compréhension de liste uniquement pour l'effet secondaire de la fonction.

Andrew Clark
la source
3
Ou si vous souhaitez conserver les entrées supprimées sous forme de dictionnaire: valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) et ainsi de suite.
kindall
Vous pouvez laisser de côté l'affectation à une variable. De telle ou telle façon, c'est la solution la plus courte et la plus pythonique et devrait être marquée comme la réponse corect à mon humble avis.
Gerhard Hagerer
12

J'ai trouvé une solution avec popetmap

d = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'b', 'c']
list(map(d.pop, keys))
print(d)

La sortie de ceci:

{'d': 'valueD'}

J'ai répondu à cette question si tard simplement parce que je pense que cela aidera à l'avenir si quelqu'un cherche la même chose. Et cela pourrait aider.

Mettre à jour

Le code ci-dessus lancera une erreur si une clé n'existe pas dans le dict.

DICTIONARY = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'l', 'c']

def remove_keys(key):
    try:
        DICTIONARY.pop(key, None)
    except:
        pass  # or do any action

list(map(remove_key, keys))
print(DICTIONARY)

production:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}
Shubham Srivastava
la source
1
Cette réponse lèvera une exception si une clé keysn'existe pas dans d- vous devrez d'abord la filtrer.
ingofreyer
@ingofreyer a mis à jour le code de gestion des exceptions. Merci d'avoir trouvé ce problème. Je pense que maintenant cela fonctionnera. :)
Shubham Srivastava
Merci, cela devrait aider tout le monde à trouver cette réponse :-)
ingofreyer
Créer une liste en tant que sous-produit de l'utilisation de la carte rend cela assez lent, il est en fait préférable de faire une boucle dessus.
Charlie Clark
4

Je n'ai aucun problème avec aucune des réponses existantes, mais j'ai été surpris de ne pas trouver cette solution:

keys_to_remove = ['a', 'b', 'c']
my_dict = {k: v for k, v in zip("a b c d e f g".split(' '), [0, 1, 2, 3, 4, 5, 6])}

for k in keys_to_remove:
    try:
        del my_dict[k]
    except KeyError:
        pass

assert my_dict == {'d': 3, 'e': 4, 'f': 5, 'g': 6}

Remarque: je suis tombé sur cette question venant d' ici . Et ma réponse est liée à cette réponse .

Doug R.
la source
3

Pourquoi pas:

entriestoremove = (2,5,1)
for e in entriestoremove:
    if d.has_key(e):
        del d[e]

Je ne sais pas ce que vous entendez par «manière plus intelligente». Il existe sûrement d'autres moyens, peut-être avec la compréhension du dictionnaire:

entriestoremove = (2,5,1)
newdict = {x for x in d if x not in entriestoremove}
L3viathan
la source
2

en ligne

import functools

#: not key(c) in d
d = {"a": "avalue", "b": "bvalue", "d": "dvalue"}

entitiesToREmove = ('a', 'b', 'c')

#: python2
map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove)

#: python3

list(map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove))

print(d)
# output: {'d': 'dvalue'}
chuang wang
la source
2

Certains tests de synchronisation pour cpython 3 montrent qu'une simple boucle for est le moyen le plus rapide, et il est assez lisible. L'ajout d'une fonction n'entraîne pas non plus beaucoup de surcharge:

résultats timeit (10k itérations):

  • all(x.pop(v) for v in r) # 0.85
  • all(map(x.pop, r)) # 0.60
  • list(map(x.pop, r)) # 0.70
  • all(map(x.__delitem__, r)) # 0.44
  • del_all(x, r) # 0.40
  • <inline for loop>(x, r) # 0.35
def del_all(mapping, to_remove):
      """Remove list of elements from mapping."""
      for key in to_remove:
          del mapping[key]

Pour les petites itérations, faire cela «en ligne» était un peu plus rapide, à cause de la surcharge de l'appel de fonction. Mais il del_allest sûr, réutilisable et plus rapide que toutes les constructions de compréhension et de mappage python.

Erik Aronesty
la source
0

Je pense que l'utilisation du fait que les clés peuvent être traitées comme un ensemble est la meilleure façon si vous êtes sur python 3:

def remove_keys(d, keys):
    to_remove = set(keys)
    filtered_keys = d.keys() - to_remove
    filtered_values = map(d.get, filtered_keys)
    return dict(zip(filtered_keys, filtered_values))

Exemple:

>>> remove_keys({'k1': 1, 'k3': 3}, ['k1', 'k2'])
{'k3': 3}
Reut Sharabani
la source
0

Ce serait bien d'avoir un support complet pour les méthodes set pour les dictionnaires (et non le désordre impie que nous obtenons avec Python 3.9) afin que vous puissiez simplement "supprimer" un ensemble de clés. Cependant, tant que ce n'est pas le cas et que vous disposez d'un dictionnaire volumineux avec potentiellement un grand nombre de clés à supprimer, vous voudrez peut-être en savoir plus sur les performances. J'ai donc créé du code qui crée quelque chose d'assez grand pour des comparaisons significatives: une matrice de 100 000 x 1000, donc 10 000,00 éléments au total.

from itertools import product
from time import perf_counter

# make a complete worksheet 100000 * 1000
start = perf_counter()
prod = product(range(1, 100000), range(1, 1000))
cells = {(x,y):x for x,y in prod}
print(len(cells))

print(f"Create time {perf_counter()-start:.2f}s")
clock = perf_counter()
# remove everything above row 50,000

keys = product(range(50000, 100000), range(1, 100))

# for x,y in keys:
#     del cells[x, y]

for n in map(cells.pop, keys):
    pass

print(len(cells))
stop = perf_counter()
print(f"Removal time {stop-clock:.2f}s")

10 millions d'articles ou plus n'est pas inhabituel dans certains contextes. En comparant les deux méthodes sur ma machine locale, je constate une légère amélioration lors de l'utilisation mapet pop, probablement à cause du moins grand nombre d'appels de fonction, mais les deux prennent environ 2,5 secondes sur ma machine. Mais cela ne fait rien par rapport au temps nécessaire pour créer le dictionnaire en premier lieu (55 s), ou en incluant des vérifications dans la boucle. Si cela est probable, il est préférable de créer un ensemble qui est une intersection des clés du dictionnaire et de votre filtre:

keys = cells.keys() & keys

En résumé: delest déjà fortement optimisé, alors ne vous inquiétez pas de l'utiliser.

Charlie Clark
la source
-1

Je suis en retard à cette discussion mais pour n'importe qui d'autre. Une solution peut être de créer une liste de clés en tant que telle.

k = ['a','b','c','d']

Ensuite, utilisez pop () dans une liste de compréhension, ou une boucle for, pour parcourir les touches et les pop une par une.

new_dictionary = [dictionary.pop(x, 'n/a') for x in k]

Le «n / a» est au cas où la clé n'existe pas, une valeur par défaut doit être retournée.

Terrance DeJesus
la source
8
new_dictionaryressemble beaucoup à une liste;)
DylanYoung