web-dev-qa-db-fra.com

Méthode efficace pour supprimer des clés avec des chaînes vides d'un dict

J'ai un dict et je voudrais 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?

86
ensnare

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’existe pas de clé dans un dict sans valeur; s'il n'avait pas de valeur, ce ne serait pas dans le dict.

143
BrenBarn

Cela peut même être 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.

67
patriciasz

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 le parcourant (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 que de nombreuses entrées ne contiennent des valeurs vides.

19
nneonneo

Si vous souhaitez une approche complète, mais succincte, de la gestion de structures de données réelles souvent imbriquées et pouvant même contenir des cycles, je vous recommande de regarder l'utilitaire de remappage du paquetage utilitaire de boltons

Après pip install boltons ou après avoir copié 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 du pur Python, il fonctionne donc partout et est entièrement testé en Python 2.7 et 3.3+. Le meilleur de tout, je l'ai écrit pour des cas comme celui-ci, donc si vous trouvez un cas qu'il ne gère pas, vous pouvez me déranger de le résoudre ici même .

11
Mahmoud Hashemi

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

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

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
8
chachan

Si vous avez un dictionnaire imbriqué et que vous voulez que cela fonctionne même pour les 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
6
Ryan

S'appuyant sur les réponses de patriciasz et nneonneo , et en tenant compte de la possibilité que vous souhaitiez supprimer des clés contenant uniquement certaines faussetés (par exemple '') mais pas d'autres (par exemple, 0) Même si vous souhaitez inclure des éléments véridiques (par exemple, 'SPAM'), vous pouvez créer une liste de résultats hautement spécifique:

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

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

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

... est évalué à False.

Maintenant, utilisez-le pour del les 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 modifier metadata à la place:

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

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 des clés avec des valeurs vides
    • aka dictionnaire de filtres pour les valeurs non vides sur chaque paire clé-valeur

Solution

  • example01 utilise la syntaxe python de compréhension de liste avec du conditionnel simple pour supprimer les valeurs "vides"

Les pièges

  • example01 fonctionne uniquement sur une copie du dictionnaire d'origine (ne pas modifier en place)
  • example01 peut produire des résultats inattendus selon ce que le développeur veut dire par "vide"
    • Est-ce que développeur veut garder les valeurs qui sont falsy ?
    • Si les valeurs du dictionnaire ne sont pas considérées 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 à gérer les pièges potentiels
  • L’approche consiste à utiliser une définition plus précise de «vide» en modifiant le conditionnel.
  • Ici, nous voulons seulement filtrer les valeurs qui sont évaluées comme des chaînes vides.
  • Ici, nous utilisons également .strip () pour filtrer les valeurs composé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
dreftymac

Pour python 3

dict((k, v) for k, v in metadata.items() if v)
3
Hector Oliveros

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

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

J'ai lu toutes les réponses dans ce fil et certaines se sont référées également à ce fil: Supprimer les signes vides du dictionnaire imbriqué avec une fonction récursive

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

Tentative 1: Trop chaud (pas performant ou à 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 quelques problèmes de performances et de compatibilité ont été soulevés dans le monde Python 2.7:

  1. utilisez isinstance au lieu de type
  2. dérouler la liste comp dans la boucle for pour plus d'efficacité
  3. utilisez python3 safe items au lieu de iteritems

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

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 ce qu'il faut (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
0
BlissRage

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
jeschwar

Certaines des méthodes mentionnées ci-dessus sont ignorées s'il existe des entiers et des valeurs float 0 et 0.0

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

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
dheeraj .A