Moyen efficace de supprimer les clés avec des chaînes vides d'un dict


116

J'ai un dict et je souhaite supprimer toutes les clés pour lesquelles il existe des chaînes de valeur vides.

metadata = {u'Composite:PreviewImage': u'(Binary data 101973 bytes)',
            u'EXIF:CFAPattern2': u''}

Quelle est la meilleure façon de procéder?

Réponses:


194

Python 2.X

dict((k, v) for k, v in metadata.iteritems() if v)

Python 2.7 - 3.X

{k: v for k, v in metadata.items() if v is not None}

Notez que toutes vos clés ont des valeurs. C'est juste que certaines de ces valeurs sont la chaîne vide. Il n'y a pas de clé dans un dict sans valeur; s'il n'avait pas de valeur, ce ne serait pas dans le dict.


29
+1. Il est important de noter que cela ne supprime pas réellement les clés d'un dictionnaire existant. Au contraire, cela crée un nouveau dictionnaire. 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é.
Steven Rumbalski

18
Cela tue également v = 0, ce qui est bien, si c'est ce que l'on veut.
Paul

2
Cela supprime également v = False, ce qui n'est pas exactement ce que OP a demandé.
Amir

4
@shredding: Vous voulez dire .items().
BrenBarn

6
Pour les versions ultérieures de python, vous devriez également utiliser le générateur de dictionnaire:{k: v for k, v in metadata.items() if v is not None}
Schiavini

75

Il peut être encore plus court que la solution de BrenBarn (et plus lisible je pense)

{k: v for k, v in metadata.items() if v}

Testé avec Python 2.7.3.


13
Cela tue également les valeurs nulles.
Paul

10
Pour conserver 0 (zéro), vous pouvez utiliser ... if v!=Nonecomme ceci: {k: v for k, v in metadata.items() if v!=None}
Dannid

1
{k: v pour k, v dans metadata.items () if v! = None} ne supprime pas les chaînes vides.
philgo20

1
La compréhension du dictionnaire n'est prise en charge qu'avec Python 2.7+ pour la compatibilité avec les versions précédentes, veuillez utiliser la solution @ BrenBarn.
Pavan Gupta

12
Doit toujours comparer None avec «n'est pas» au lieu de «! =». stackoverflow.com/a/14247419/2368836
rocktheartsm4l

21

Si vous avez vraiment besoin de modifier le dictionnaire d'origine:

empty_keys = [k for k,v in metadata.iteritems() if not v]
for k in empty_keys:
    del metadata[k]

Notez que nous devons faire une liste des clés vides car nous ne pouvons pas modifier un dictionnaire en l'itérant (comme vous l'avez peut-être remarqué). Cela coûte moins cher (en termes de mémoire) que de créer un tout nouveau dictionnaire, à moins qu'il n'y ait beaucoup d'entrées avec des valeurs vides.


cela supprimera également la valeur 0 et 0 n'est pas vide
JVK

2
Si vous utilisez Python 3+ que vous devez remplacer .iteritems()par .items(), le premier ne fonctionne plus dans les dernières versions de Python.
Mariano Ruiz

12

La solution de BrenBarn est idéale (et pythonique, pourrais-je ajouter). Voici cependant une autre solution (fp):

from operator import itemgetter
dict(filter(itemgetter(1), metadata.items()))

12

Si vous souhaitez une approche complète, mais succincte, de la gestion des structures de données du monde réel qui sont souvent imbriquées et peuvent même contenir des cycles, je vous recommande de consulter l'utilitaire de remappage du package d'utilitaires boltons .

Après pip install boltonsou en copiant iterutils.py dans votre projet, faites simplement:

from boltons.iterutils import remap

drop_falsey = lambda path, key, value: bool(value)
clean = remap(metadata, visit=drop_falsey)

Cette page contient de nombreux autres exemples, y compris ceux fonctionnant avec des objets beaucoup plus volumineux de l'API de Github.

C'est pur-Python, donc cela fonctionne partout, et est entièrement testé en Python 2.7 et 3.3+. Mieux encore, je l'ai écrit pour exactement des cas comme celui-ci, donc si vous trouvez un cas qu'il ne gère pas, vous pouvez me déranger pour le réparer ici .


1
Cette solution a très bien fonctionné pour un problème similaire que j'avais: supprimer les valeurs vides des listes profondément imbriquées dans les dictionnaires. Merci!
Nicholas Tulach

1
C'est bien, car vous ne réinventez pas la roue et fournissez une solution pour les objets imbriqués. Merci!
vekerdyb

1
J'ai beaucoup aimé l'article que vous avez écrit pour votre bibliothèque, et c'est une bibliothèque utile!
lifelogger

11

Basé sur la solution de Ryan , si vous avez également des listes et des dictionnaires imbriqués:

Pour Python 2:

def remove_empty_from_dict(d):
    if type(d) is dict:
        return dict((k, remove_empty_from_dict(v)) for k, v in d.iteritems() if v and remove_empty_from_dict(v))
    elif type(d) is list:
        return [remove_empty_from_dict(v) for v in d if v and remove_empty_from_dict(v)]
    else:
        return d

Pour Python 3:

def remove_empty_from_dict(d):
    if type(d) is dict:
        return dict((k, remove_empty_from_dict(v)) for k, v in d.items() if v and remove_empty_from_dict(v))
    elif type(d) is list:
        return [remove_empty_from_dict(v) for v in d if v and remove_empty_from_dict(v)]
    else:
        return d

1
Ha, belle extension! C'est une bonne solution pour les dictionnaires comme les suivants:d = { "things": [{ "name": "" }] }
Ryan Shea

6

Si vous avez un dictionnaire imbriqué et que vous voulez que cela fonctionne même pour des sous-éléments vides, vous pouvez utiliser une variante récursive de la suggestion de BrenBarn:

def scrub_dict(d):
    if type(d) is dict:
        return dict((k, scrub_dict(v)) for k, v in d.iteritems() if v and scrub_dict(v))
    else:
        return d

Utiliser à la items()place de iteritems()pour Python 3
andydavies

6

Réponse rapide (TL; DR)

Exemple01

### example01 -------------------

mydict  =   { "alpha":0,
              "bravo":"0",
              "charlie":"three",
              "delta":[],
              "echo":False,
              "foxy":"False",
              "golf":"",
              "hotel":"   ",                        
            }
newdict =   dict([(vkey, vdata) for vkey, vdata in mydict.iteritems() if(vdata) ])
print newdict

### result01 -------------------
result01 ='''
{'foxy': 'False', 'charlie': 'three', 'bravo': '0'}
'''

Réponse détaillée

Problème

  • Contexte: Python 2.x
  • Scénario: le développeur souhaite modifier un dictionnaire pour exclure les valeurs vides
    • aka supprimer les valeurs vides d'un dictionnaire
    • aka supprimer les clés avec des valeurs vides
    • aka dictionnaire de filtres pour les valeurs non vides sur chaque paire clé-valeur

Solution

  • exemple01 utiliser la syntaxe de compréhension de liste python avec un conditionnel simple pour supprimer les valeurs «vides»

Pièges

  • example01 ne fonctionne que sur une copie du dictionnaire original (ne modifie pas en place)
  • example01 peut produire des résultats inattendus selon ce que le développeur entend par "vide"
    • Le développeur veut-il conserver des valeurs fausses ?
    • Si les valeurs du dictionnaire ne sont pas garanties comme des chaînes, le développeur peut subir une perte de données inattendue.
    • result01 montre que seules trois paires clé-valeur ont été préservées de l'ensemble d'origine

Autre exemple

  • example02 aide à surmonter les pièges potentiels
  • L'approche consiste à utiliser une définition plus précise de «vide» en changeant le conditionnel.
  • Ici, nous ne voulons filtrer que les valeurs qui correspondent à des chaînes vides.
  • Ici, nous utilisons également .strip () pour filtrer les valeurs constituées uniquement d'espaces.

Exemple02

### example02 -------------------

mydict  =   { "alpha":0,
              "bravo":"0",
              "charlie":"three",
              "delta":[],
              "echo":False,
              "foxy":"False",
              "golf":"",
              "hotel":"   ",
            }
newdict =   dict([(vkey, vdata) for vkey, vdata in mydict.iteritems() if(str(vdata).strip()) ])
print newdict

### result02 -------------------
result02 ='''
{'alpha': 0,
  'bravo': '0', 
  'charlie': 'three', 
  'delta': [],
  'echo': False,
  'foxy': 'False'
  }
'''

Voir également



4

En vous basant sur les réponses de patriciasz et nneonneo , et en tenant compte de la possibilité que vous souhaitiez supprimer des clés qui n'ont que certaines fausses choses (par exemple '') mais pas d'autres (par exemple 0), ou peut-être que vous voulez même inclure des choses véridiques (par exemple 'SPAM') , vous pouvez alors créer une liste de résultats très spécifique:

unwanted = ['', u'', None, False, [], 'SPAM']

Malheureusement, cela ne fonctionne pas tout à fait, car par exemple 0 in unwantedévalue à True. Nous devons faire la distinction entre les 0choses fausses et d'autres, nous devons donc utiliser is:

any([0 is i for i in unwanted])

... évalue à False.

Maintenant, utilisez-le pour delles choses indésirables:

unwanted_keys = [k for k, v in metadata.items() if any([v is i for i in unwanted])]
for k in unwanted_keys: del metadata[k]

Si vous voulez un nouveau dictionnaire, au lieu de le modifier metadatasur place:

newdict = {k: v for k, v in metadata.items() if not any([v is i for i in unwanted])}

vraiment joli cliché, il aborde de nombreux problèmes à la fois et il résout la question, merci de le préciser
jlandercy

Cool! Cela fonctionne pour cet exemple. Cependant, cela ne fonctionne pas lorsqu'un élément du dictionnaire est[]
jsga

2

J'ai lu toutes les réponses dans ce fil et certaines se sont également référées à ce fil: Supprimez les dicts vides dans le dictionnaire imbriqué avec fonction récursive

J'ai initialement utilisé la solution ici et cela a très bien fonctionné:

Tentative 1: Trop chaud (pas performant ni à l'épreuve du temps) :

def scrub_dict(d):
    if type(d) is dict:
        return dict((k, scrub_dict(v)) for k, v in d.iteritems() if v and scrub_dict(v))
    else:
        return d

Mais certains problèmes de performances et de compatibilité ont été soulevés dans le monde Python 2.7:

  1. utiliser isinstanceau lieu detype
  2. dérouler la liste comp en forboucle pour plus d'efficacité
  3. utilisez python3 safe itemsau lieu deiteritems

Tentative 2: Trop froid (manque de mémorisation) :

def scrub_dict(d):
    new_dict = {}
    for k, v in d.items():
        if isinstance(v,dict):
            v = scrub_dict(v)
        if not v in (u'', None, {}):
            new_dict[k] = v
    return new_dict

DOH! Ce n'est pas récursif et pas du tout mémoizant.

Tentative 3: juste bien (jusqu'à présent) :

def scrub_dict(d):
    new_dict = {}
    for k, v in d.items():
        if isinstance(v,dict):
            v = scrub_dict(v)
        if not v in (u'', None, {}):
            new_dict[k] = v
    return new_dict

1
sauf si je suis aveugle, il me semble que les
essais

1

Dicts mélangés à des tableaux

  • La réponse à tentative 3: Just Right (pour l'instant) de la réponse de BlissRage ne gère pas correctement les éléments des tableaux. J'inclus un patch au cas où quelqu'un en aurait besoin. La méthode gère la liste avec le bloc d'instructions de if isinstance(v, list):, qui nettoie la liste en utilisant l' scrub_dict(d)implémentation d' origine .
    @staticmethod
    def scrub_dict(d):
        new_dict = {}
        for k, v in d.items():
            if isinstance(v, dict):
                v = scrub_dict(v)
            if isinstance(v, list):
                v = scrub_list(v)
            if not v in (u'', None, {}):
                new_dict[k] = v
        return new_dict

    @staticmethod
    def scrub_list(d):
        scrubbed_list = []
        for i in d:
            if isinstance(i, dict):
                i = scrub_dict(i)
            scrubbed_list.append(i)
        return scrubbed_list

impressionnant . . .
J'avais

0

Une autre façon de procéder consiste à utiliser la compréhension du dictionnaire. Cela devrait être compatible avec2.7+

result = {
    key: value for key, value in
    {"foo": "bar", "lorem": None}.items()
    if value
}

0

Voici une option si vous utilisez pandas:

import pandas as pd

d = dict.fromkeys(['a', 'b', 'c', 'd'])
d['b'] = 'not null'
d['c'] = ''  # empty string

print(d)

# convert `dict` to `Series` and replace any blank strings with `None`;
# use the `.dropna()` method and
# then convert back to a `dict`
d_ = pd.Series(d).replace('', None).dropna().to_dict()

print(d_)

0

Certaines des méthodes mentionnées ci-dessus ignorent s'il y a des entiers et des flottants avec des valeurs 0 et 0,0

Si quelqu'un veut éviter ce qui précède, vous pouvez utiliser le code ci-dessous (supprime les chaînes vides et les valeurs None du dictionnaire imbriqué et de la liste imbriquée):

def remove_empty_from_dict(d):
    if type(d) is dict:
        _temp = {}
        for k,v in d.items():
            if v == None or v == "":
                pass
            elif type(v) is int or type(v) is float:
                _temp[k] = remove_empty_from_dict(v)
            elif (v or remove_empty_from_dict(v)):
                _temp[k] = remove_empty_from_dict(v)
        return _temp
    elif type(d) is list:
        return [remove_empty_from_dict(v) for v in d if( (str(v).strip() or str(remove_empty_from_dict(v)).strip()) and (v != None or remove_empty_from_dict(v) != None))]
    else:
        return d

0

«Comme j'écris actuellement une application de bureau pour mon travail avec Python, j'ai trouvé dans l'application de saisie de données quand il y a beaucoup d'entrées et que certaines ne sont pas obligatoires, l'utilisateur peut donc le laisser vide, à des fins de validation, il est facile de saisir toutes les entrées, puis supprimer la clé vide ou la valeur d'un dictionnaire. Donc, mon code ci-dessus montre comment nous pouvons facilement les supprimer, en utilisant la compréhension du dictionnaire et en conservant l'élément de valeur du dictionnaire qui n'est pas vide. J'utilise Python 3.8.3

data = {'':'', '20':'', '50':'', '100':'1.1', '200':'1.2'}

dic = {key:value for key,value in data.items() if value != ''}

print(dic)

{'100': '1.1', '200': '1.2'}

Veuillez mentionner la version python également supportera-t-elle la dernière version?
HaseeB Mir

Votre réponse est actuellement signalée comme étant de mauvaise qualité et peut être supprimée. Veuillez vous assurer que votre réponse contient une explication en dehors de tout code.
Tim Stack le

@TimStack Veuillez recommander la suppression des réponses LQ.
10 Rep

@ 10Rep Je ne recommanderai pas la suppression pour une réponse qui peut fonctionner comme une solution mais qui manque simplement de commentaires descriptifs. Je préfère informer l'utilisateur et lui apprendre à quoi ressemble une meilleure réponse.
Tim Stack le

@HasseB Mir J'utilise le dernier Python 3.8.3
KokoEfraim le

-2

Quelques analyses comparatives:

1. Compréhension de liste recréer dict

In [7]: %%timeit dic = {str(i):i for i in xrange(10)}; dic['10'] = None; dic['5'] = None
   ...: dic = {k: v for k, v in dic.items() if v is not None} 
   1000000 loops, best of 7: 375 ns per loop

2. Compréhension de liste recréer dict en utilisant dict ()

In [8]: %%timeit dic = {str(i):i for i in xrange(10)}; dic['10'] = None; dic['5'] = None
   ...: dic = dict((k, v) for k, v in dic.items() if v is not None)
1000000 loops, best of 7: 681 ns per loop

3. Bouclez et supprimez la clé si v est Aucun

In [10]: %%timeit dic = {str(i):i for i in xrange(10)}; dic['10'] = None; dic['5'] = None
    ...: for k, v in dic.items():
    ...:   if v is None:
    ...:     del dic[k]
    ...: 
10000000 loops, best of 7: 160 ns per loop

Ainsi, la boucle et la suppression sont les plus rapides à 160ns, la compréhension de la liste est deux fois moins lente à ~ 375ns et avec un appel à dict()est à nouveau deux fois moins lente à ~ 680ns.

L'emballage de 3 dans une fonction le ramène à environ 275 ns. Aussi pour moi PyPy était environ deux fois plus rapide que neet python.


La boucle et la suppression peuvent également générer une RunTimeError, car il n'est pas valide de modifier un dictionnaire lors de l'itération d'une vue. docs.python.org/3/library/stdtypes.html s4.10.1
Airsource Ltd

ah man ouais ok en python 3 c'est vrai mais pas en python 2.7 car les items retournent une liste, donc vous devez appeler list(dic.items())py 3. Dict compréhension ftw alors? del semble toujours plus rapide pour un faible ratio de valeurs nulles / vides. Je suppose que la construction de cette liste est tout aussi mauvaise pour la consommation de mémoire que la simple recréation du dict.
Richard Mathie
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.