Supprimer un élément d'un dictionnaire

1396

Existe-t-il un moyen de supprimer un élément d'un dictionnaire en Python?

De plus, comment puis-je supprimer un élément d'un dictionnaire pour renvoyer une copie (c'est-à-dire sans modifier l'original)?

richzilla
la source
13
Pourquoi avez-vous besoin d'une fonction qui renvoie un dictionnaire, alors que vous pouvez simplement modifier le dictionnaire directement?
amillerrhodes
5
Le dictionnaire popméthode modifie le dictionnaire en place . Par conséquent, il modifie la référence au dictionnaire qui a été transmise de l'appelant à la "fonction d'assistance". Ainsi, la "fonction d'assistance" n'a pas besoin de renvoyer quoi que ce soit, car la référence d'origine au dictionnaire dans l'appelant sera déjà modifiée. N'attribuez le retour de dict.pop()à rien si vous n'en avez pas besoin. EG: do stuff with my_dict; my_dict.pop(my_key, None); do more stuff with my_dict # now doesn't have my_key. Utilisez deepcopy(my_dict)si nécessaire.
Mark Mikofski
1
Étant donné que le titre original n'était pas d'accord avec les détails et excluait spécifiquement la solution évidente d.pop(), j'ai fixé le titre pour poser la question spécifiée dans les détails.
smci
1
Nous devrions ajouter une mise en garde vous demandant si vous voulez vraiment le faire, comme si vous le faites N fois sur un dictionnaire avec des éléments E, vous fuierez (/ utiliserez) la mémoire O (N * E) avec toutes les copies complètes. Si vous voulez simplement une lecture seule (copie superficielle), faites-le d.pop(key). Mais si quelque chose modifie jamais la copie superficielle, vous avez un problème bien connu avec l'aliasing . Cela aide si vous nous dites le contexte plus large. (Y a-t-il autre chose qui modifie les valeurs de dict? Essayez-vous d'itérer de manière destructive une liste?
Sinon
5
"Pourquoi avez-vous besoin d'une fonction qui renvoie un dictionnaire, alors que vous pouvez simplement modifier le dictionnaire directement?" Peut-être parce que vous voulez écrire des fonctions pures qui ne modifient pas leurs paramètres?
Gene Callahan

Réponses:

1729

L' delinstruction supprime un élément:

del d[key]

Cependant, cela mute le dictionnaire existant de sorte que le contenu du dictionnaire change pour quiconque ayant une référence à la même instance. Pour renvoyer un nouveau dictionnaire, faites une copie du dictionnaire:

def removekey(d, key):
    r = dict(d)
    del r[key]
    return r

Le dict()constructeur fait une copie superficielle . Pour faire une copie complète, voir le copymodule .


Notez que faire une copie pour chaque dict del/ affectation / etc. signifie que vous passez d'un temps constant à un temps linéaire, et que vous utilisez également un espace linéaire. Pour les petits dict, ce n'est pas un problème. Mais si vous prévoyez de faire beaucoup de copies de grands dict, vous voulez probablement une structure de données différente, comme un HAMT (comme décrit dans cette réponse ).

Greg Hewgill
la source
15
c'est un grand point sur la mutabilité des dictionnaires +1 - bien que je ne puisse pas penser à un moment où je voulais des copies du dictionnaire, j'ai toujours compté sur la copie de «tout le monde» étant la même. bon point.
tMC
30
@tMC Si vous modifiez le dictpendant que vous le parcourez, cela vous donnera une erreur:RuntimeError: dictionary changed size during iteration
VertigoRay
15
Et la popméthode qui en fait fait de même? N'est-ce pas plus pythonique? (étant la méthode de dict, pas un mot réservé spécial)?
Serge
21
Cette réponse a une faiblesse, elle pourrait être trompeuse. Les lecteurs peuvent mal comprendre que dict (d) peut leur donner une copie avec 'd'. Mais c'est une copie incomplète. Lorsque vous effectuez uniquement des opérations de suppression de touches, c'est OK. Mais lorsque vous voulez faire autre chose à un dict imbriqué, la modification de «r» à l'aide de cette méthode de copie peut entraîner la modification du «d» d'origine. Pour obtenir une copie authentique, vous devez d'abord «importer une copie», puis «r = copy.deepcopy (d)».
Zen
10
@Zen: Très bien, j'ai ajouté une note sur la copie superficielle vs profonde.
Greg Hewgill
264

pop mute le dictionnaire.

 >>> lol = {"hello": "gdbye"}
 >>> lol.pop("hello")
     'gdbye'
 >>> lol
     {}

Si vous souhaitez conserver l'original, vous pouvez simplement le copier.

Cristal
la source
29
"del" est ok, mais "pop" semble plus "Pythonic", à mon avis.
ivanleoncz
1
@ivanleoncz pourquoi?
kevr
3
poprenvoie la valeur qui a été «sautée», ce qui vous permet d'utiliser cette valeur pour une autre raison. Si ce n'est pas plus "Pythonic", je dirais que ça semble mieux, c'est sûr :). Ce n'est pas un dicton, mais cela fonctionne de la même manière pour les deux: github.com/ivanlmj/python-prototypes/blob/master/3.4/…
ivanleoncz
2
@ivanleoncz C'est aussi mieux pour une raison de plus, poppeut être fourni avec une valeur par défaut qui sera retournée lorsqu'une clé est manquante dans dict. C'est bien quand vous devez retirer quelques clés mais certaines d'entre elles peuvent être manquantes; deljetterait KeyErrordans un tel cas.
Itachi
80

Je pense que votre solution est la meilleure façon de le faire. Mais si vous voulez une autre solution, vous pouvez créer un nouveau dictionnaire en utilisant les clés de l'ancien dictionnaire sans inclure votre clé spécifiée, comme ceci:

>>> a
{0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
>>> {i:a[i] for i in a if i!=0}
{1: 'one', 2: 'two', 3: 'three'}
utdemir
la source
2
Vraiment cool. J'aime la méthode rapide pour filtrer un dictionnaire sans définir de nouvelle fonction.
Joe J
6
Pour ceux qui ne sont pas familiers avec les compréhensions, vous pouvez également faire quelque chose comme ceci: {i:a[i] for i in a if i not in [0, 1, 2]}si vous souhaitez supprimer plusieurs éléments.
kmatheny
9
Ce serait mieux {k:v for k,v in a.items() if k != 0}je pense.
rlbond
1
La meilleure solution pour supprimer un élément par clé et renvoyer le résultat du nouveau dict sur la même ligne. Par exemple, si vous devez utiliser un dict déjà construit sans un seul élément comme **kwargs,some_function(**{k:v for k,v in some_dict.items() if k not 'some_key'})
Cole
La meilleure solution ici. Un liner et il ne mute pas le dictionnaire d'origine.
Andrew Winterbotham
55

L' instruction del est ce que vous recherchez. Si vous avez un dictionnaire nommé foo avec une clé appelée «bar», vous pouvez supprimer «bar» de foo comme ceci:

del foo['bar']

Notez que cela modifie de façon permanente le dictionnaire utilisé. Si vous souhaitez conserver le dictionnaire d'origine, vous devrez au préalable en créer une copie:

>>> foo = {'bar': 'baz'}
>>> fu = dict(foo)
>>> del foo['bar']
>>> print foo
{}
>>> print fu
{'bar': 'baz'}

L' dictappel fait une copie superficielle. Si vous voulez une copie complète, utilisezcopy.deepcopy .

Voici une méthode que vous pouvez copier et coller, pour votre commodité:

def minus_key(key, dictionary):
    shallow_copy = dict(dictionary)
    del shallow_copy[key]
    return shallow_copy
arussell84
la source
1
@ pythonian29033, en fait, non . La réponse acceptée fonctionne comme prévu - elle renvoie le dict sans une touche. L'approche de cette réponse mute le dict original;) ​​Il y a une différence significative
maxkoryukov
1
@ arussell84, pourquoi >>>est-il souvent utilisé dans les exemples python? Oui, python-doc contient beaucoup de ces choses. Mais un tel code n'est pas pratique pour le copypaste . Je suis confus ...
maxkoryukov
@maxkoryukov yup il le fait! mais cette fonction et cette réponse est exactement la même, à l'exception de cette réponse étant à l'intérieur d'une fonction. et vous ne devez pas coder en python depuis un moment, >>>imite la notation d'écoute de python en mode cli
pythonian29033
2
@ pythonian29033 à propos >>>. Oui, c'est du style REPL, mais parlons franchement: le seul homme avait écrit cet échantillon, et 1000 l'ont lu. Je pense que ce serait formidable d'écrire des exemples de manière à permettre une copie et une exécution faciles. Je n'aime pas retirer ces équerres à la main. Ou copier ligne par ligne .. Donc je ne comprends pas: pourquoi ces angles sont toujours là))) Peut-être que je ne sais pas quelque chose?
maxkoryukov
3
J'ai ajouté une fonction qui peut être copiée / collée, pour votre commodité.
arussell84
48

Il y a beaucoup de bonnes réponses, mais je tiens à souligner une chose.

Vous pouvez utiliser à la fois la dict.pop()méthode et une instruction plus génériquedel pour supprimer des éléments d'un dictionnaire. Ils mutent tous les deux le dictionnaire d'origine, vous devez donc en faire une copie (voir les détails ci-dessous).

Et les deux lèveront un KeyErrorsi la clé que vous leur fournissez n'est pas présente dans le dictionnaire:

key_to_remove = "c"
d = {"a": 1, "b": 2}
del d[key_to_remove]  # Raises `KeyError: 'c'`

et

key_to_remove = "c"
d = {"a": 1, "b": 2}
d.pop(key_to_remove)  # Raises `KeyError: 'c'`

Vous devez vous en occuper:

en capturant l'exception:

key_to_remove = "c"
d = {"a": 1, "b": 2}
try:
    del d[key_to_remove]
except KeyError as ex:
    print("No such key: '%s'" % ex.message)

et

key_to_remove = "c"
d = {"a": 1, "b": 2}
try:
    d.pop(key_to_remove)
except KeyError as ex:
    print("No such key: '%s'" % ex.message)

en effectuant une vérification:

key_to_remove = "c"
d = {"a": 1, "b": 2}
if key_to_remove in d:
    del d[key_to_remove]

et

key_to_remove = "c"
d = {"a": 1, "b": 2}
if key_to_remove in d:
    d.pop(key_to_remove)

mais avec pop()il y a aussi un moyen beaucoup plus concis - fournissez la valeur de retour par défaut:

key_to_remove = "c"
d = {"a": 1, "b": 2}
d.pop(key_to_remove, None)  # No `KeyError` here

Sauf si vous utilisez pop()pour obtenir la valeur d'une clé supprimée, vous pouvez fournir quoi que ce soit, non nécessaire None. Bien qu'il soit possible que l'utilisation delavec invérification soit légèrement plus rapide en raison du fait qu'elle pop()est une fonction avec ses propres complications entraînant des frais généraux. Habituellement, ce n'est pas le cas, donc pop()avec une valeur par défaut, c'est assez bon.


Quant à la question principale, vous devrez faire une copie de votre dictionnaire, pour enregistrer le dictionnaire d'origine et en avoir un nouveau sans que la clé ne soit supprimée.

D'autres personnes suggèrent ici de faire une copie complète (profonde) avec copy.deepcopy(), ce qui pourrait être une exagération, une copie "normale" (peu profonde), en utilisant copy.copy()oudict.copy() , pourrait être suffisant. Le dictionnaire conserve une référence à l'objet comme valeur pour une clé. Ainsi, lorsque vous supprimez une clé d'un dictionnaire, cette référence est supprimée, pas l'objet référencé. L'objet lui-même peut être supprimé plus tard automatiquement par le garbage collector, s'il n'y a pas d'autres références pour lui dans la mémoire. Faire une copie complète nécessite plus de calculs par rapport à une copie superficielle, donc cela diminue les performances du code en faisant la copie, en gaspillant de la mémoire et en fournissant plus de travail au GC, parfois une copie superficielle suffit.

Cependant, si vous avez des objets mutables en tant que valeurs de dictionnaire et prévoyez de les modifier plus tard dans le dictionnaire renvoyé sans la clé, vous devez effectuer une copie complète.

Avec copie superficielle:

def get_dict_wo_key(dictionary, key):
    """Returns a **shallow** copy of the dictionary without a key."""
    _dict = dictionary.copy()
    _dict.pop(key, None)
    return _dict


d = {"a": [1, 2, 3], "b": 2, "c": 3}
key_to_remove = "c"

new_d = get_dict_wo_key(d, key_to_remove)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3], "b": 2}
new_d["a"].append(100)
print(d)  # {"a": [1, 2, 3, 100], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2}
new_d["b"] = 2222
print(d)  # {"a": [1, 2, 3, 100], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2222}

Avec copie profonde:

from copy import deepcopy


def get_dict_wo_key(dictionary, key):
    """Returns a **deep** copy of the dictionary without a key."""
    _dict = deepcopy(dictionary)
    _dict.pop(key, None)
    return _dict


d = {"a": [1, 2, 3], "b": 2, "c": 3}
key_to_remove = "c"

new_d = get_dict_wo_key(d, key_to_remove)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3], "b": 2}
new_d["a"].append(100)
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2}
new_d["b"] = 2222
print(d)  # {"a": [1, 2, 3], "b": 2, "c": 3}
print(new_d)  # {"a": [1, 2, 3, 100], "b": 2222}
Nikita
la source
21

… Comment puis-je supprimer un élément d'un dictionnaire pour renvoyer une copie (c'est-à-dire sans modifier l'original)?

A dictest la mauvaise structure de données à utiliser pour cela.

Bien sûr, copier le dict et sauter à partir de la copie fonctionne, tout comme la construction d'un nouveau dict avec une compréhension, mais tout cela prend du temps - vous avez remplacé une opération à temps constant par une opération à temps linéaire. Et toutes ces copies vivantes prennent à la fois de l'espace - un espace linéaire par copie.

D'autres structures de données, comme les essais mappés de tableaux de hachage , sont conçues exactement pour ce type de cas d'utilisation: l'ajout ou la suppression d'un élément renvoie une copie en temps logarithmique, partageant la majeure partie de son stockage avec l'original . 1

Bien sûr, il y a quelques inconvénients. Les performances sont logarithmiques plutôt que constantes (bien qu'avec une grande base, généralement 32-128). Et, bien que vous puissiez rendre l'API non mutante identique à dict, l'API "mutante" est évidemment différente. Et, surtout, il n'y a pas de piles HAMT incluses avec Python. 2

La pyrsistentbibliothèque est une implémentation assez solide des remplacements de dict basés sur HAMT (et divers autres types) pour Python. Il a même une API évolutive astucieuse pour porter le code de mutation existant vers du code persistant aussi facilement que possible. Mais si vous voulez être explicite sur le retour des copies plutôt que sur la mutation, vous devez simplement l'utiliser comme ceci:

>>> from pyrsistent import m
>>> d1 = m(a=1, b=2)
>>> d2 = d1.set('c', 3)
>>> d3 = d1.remove('a')
>>> d1
pmap({'a': 1, 'b': 2})
>>> d2
pmap({'c': 3, 'a': 1, 'b': 2})
>>> d3
pmap({'b': 2})

C'est d3 = d1.remove('a')exactement ce que demande la question.

Si vous avez des structures de données mutables comme dictet listintégrées dans le pmap, vous aurez toujours des problèmes d'alias - vous ne pouvez résoudre ce problème qu'en allant immuable jusqu'en bas, en incorporant les pmaps et les pvectors.


1. Les HAMT sont également devenus populaires dans des langages comme Scala, Clojure, Haskell parce qu'ils jouent très bien avec la programmation sans verrouillage et la mémoire transactionnelle logicielle, mais aucun de ceux-ci n'est très pertinent en Python.

2. En fait, il y a un HAMT dans le stdlib, utilisé dans l'implémentation de contextvars. Le PEP retiré précédemment explique pourquoi. Mais c'est un détail d'implémentation caché de la bibliothèque, pas un type de collection publique.

abarnert
la source
19
d = {1: 2, '2': 3, 5: 7}
del d[5]
print 'd = ', d

Résultat: d = {1: 2, '2': 3}

satels
la source
14

Appelez simplement del d ['clé'].

Cependant, en production, c'est toujours une bonne pratique de vérifier si la «clé» existe en d.

if 'key' in d:
    del d['key']
Khanh Hua
la source
7
Hmm, non, en production, il vaut mieux suivre l' idéologie EAFP . Retirez simplement la clé dans le try-exceptbloc. Au moins, ce sera une opération atomique;)
maxkoryukov
1
Et si vous voulez être concis, utilisez d.pop('key', None)-le. Mais la vraie question était d'obtenir le dictionnaire sans une clé, et non de modifier le dict. Donc les compréhensions - est un bon choix ici;)
maxkoryukov
7

Non, il n'y a pas d'autre moyen que

def dictMinus(dct, val):
   copy = dct.copy()
   del copy[val]
   return copy

Cependant, la création de copies de dictionnaires légèrement modifiés n'est souvent pas une bonne idée car elle entraînera des demandes de mémoire relativement importantes. Il est généralement préférable d'enregistrer l'ancien dictionnaire (si nécessaire), puis de le modifier.

phihag
la source
7
# mutate/remove with a default
ret_val = body.pop('key', 5)
# no mutation with a default
ret_val = body.get('key', 5)
daino3
la source
5
>>> def delete_key(dict, key):
...     del dict[key]
...     return dict
... 
>>> test_dict = {'one': 1, 'two' : 2}
>>> print delete_key(test_dict, 'two')
{'one': 1}
>>>

cela ne fait aucune gestion des erreurs, il suppose que la clé est dans le dict, vous voudrez peut-être le vérifier d'abord et raisesi ce n'est pas le cas

tMC
la source
10
En quoi votre méthode est-elle différente de la simple del test_dict[key]?
Mad Physicist
5

Voici une approche de conception de haut niveau:

def eraseElement(d,k):
    if isinstance(d, dict):
        if k in d:
            d.pop(k)
            print(d)
        else:
            print("Cannot find matching key")
    else:
        print("Not able to delete")


exp = {'A':34, 'B':55, 'C':87}
eraseElement(exp, 'C')

Je passe le dictionnaire et la clé que je veux dans ma fonction, valide si c'est un dictionnaire et si la clé est correcte, et si les deux existent, supprime la valeur du dictionnaire et imprime les restes.

Production: {'B': 55, 'A': 34}

J'espère que cela pourra aider!

atlas
la source
3

L'extrait de code ci-dessous vous aidera certainement, j'ai ajouté des commentaires dans chaque ligne qui vous aideront à comprendre le code.

def execute():
   dic = {'a':1,'b':2}
   dic2 = remove_key_from_dict(dic, 'b')  
   print(dict2)           # {'a': 1}
   print(dict)            # {'a':1,'b':2}

def remove_key_from_dict(dictionary_to_use, key_to_delete):
   copy_of_dict = dict(dictionary_to_use)     # creating clone/copy of the dictionary
   if key_to_delete in copy_of_dict :         # checking given key is present in the dictionary
       del copy_of_dict [key_to_delete]       # deleting the key from the dictionary 
   return copy_of_dict                        # returning the final dictionary

ou vous pouvez également utiliser dict.pop ()

d = {"a": 1, "b": 2}

res = d.pop("c")  # No `KeyError` here
print (res)       # this line will not execute

ou la meilleure approche est

res = d.pop("c", "key not found")
print (res)   # key not found
print (d)     # {"a": 1, "b": 2}

res = d.pop("b", "key not found")
print (res)   # 2
print (d)     # {"a": 1}
Mayur Agarwal
la source
2

Voici une autre variante utilisant la compréhension de liste:

original_d = {'a': None, 'b': 'Some'}
d = dict((k,v) for k, v in original_d.iteritems() if v)
# result should be {'b': 'Some'}

L'approche est basée sur une réponse de ce post: moyen efficace pour supprimer des clés avec des chaînes vides d'un dict

BigBlueHat
la source
1
Si vous allez répondre à une question vieille de plusieurs années qui a déjà une réponse simple, appropriée et acceptée, assurez-vous au moins que votre réponse est correcte. Cela ne fait pas ce que le PO a demandé.
user2357112 prend en charge Monica
Je ne vérifie généralement pas les dates des questions qui, je pense, pourraient contenir des informations précieuses. De plus, selon l'un des commentaires sur la question à laquelle j'ai lié: "Habituellement, c'est exactement ce que quelqu'un veut et c'est probablement ce dont le PO a besoin, mais ce n'est pas ce que le PO a demandé" stackoverflow.com/questions/12118695/… I savait que ce n'était pas une réponse directe à la question; plutôt une extension des options.
BigBlueHat
3
Cette réponse, bien que incomplète, nous permet d'apprendre que nous pouvons également supprimer des éléments avec des conditions if. juste changer if vpour if k is not 'a'répondre à l'op. Mais je ne pense pas que ce soit un moyen efficace, cela supprime l'élément dans O (n) plutôt que O (log n) comme pop ou del.
holgac
0
    species = {'HI': {'1': (1215.671, 0.41600000000000004),
  '10': (919.351, 0.0012),
  '1025': (1025.722, 0.0791),
  '11': (918.129, 0.0009199999999999999),
  '12': (917.181, 0.000723),
  '1215': (1215.671, 0.41600000000000004),
  '13': (916.429, 0.0005769999999999999),
  '14': (915.824, 0.000468),
  '15': (915.329, 0.00038500000000000003),
 'CII': {'1036': (1036.3367, 0.11900000000000001), '1334': (1334.532, 0.129)}}

Le code suivant fera une copie de dict specieset supprimera les éléments qui ne sont pas danstrans_HI

trans_HI=['1025','1215']
for transition in species['HI'].copy().keys():
    if transition not in trans_HI:
        species['HI'].pop(transition)
Sameeresque
la source