Modo efficiente per rimuovere le chiavi con stringhe vuote da un dict


116

Ho un dict e vorrei rimuovere tutte le chiavi per le quali sono presenti stringhe di valori vuote.

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

Qual è il modo migliore per farlo?

Risposte:


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}

Tieni presente che tutte le tue chiavi hanno valori. È solo che alcuni di questi valori sono la stringa vuota. Non esiste una chiave in un dict senza valore; se non avesse un valore, non sarebbe nel dict.


29
+1. È importante notare che ciò non rimuove effettivamente le chiavi da un dizionario esistente. Piuttosto, crea un nuovo dizionario. Di solito questo è esattamente ciò che qualcuno vuole ed è probabilmente ciò di cui l'OP ha bisogno, ma non è ciò che l'OP ha chiesto.
Steven Rumbalski

18
Questo uccide anche v = 0, che va bene, se è quello che si vuole.
Paul

2
Questo elimina anche v = False, che non è esattamente ciò che OP ha chiesto.
Amir

4
@shredding: Vuoi dire .items().
BrenBarn

6
Per le versioni successive di python dovresti usare anche il generatore di dizionari:{k: v for k, v in metadata.items() if v is not None}
Schiavini

75

Può diventare ancora più breve della soluzione di BrenBarn (e più leggibile credo)

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

Testato con Python 2.7.3.


13
Questo uccide anche i valori zero.
Paul

10
Per conservare 0 (zero) puoi usare in questo ... if v!=Nonemodo: {k: v for k, v in metadata.items() if v!=None}
Dannid

1
{k: v for k, v in metadata.items () if v! = None} non elimina le stringhe vuote.
philgo20

1
la comprensione del dizionario è supportata solo con Python 2.7+ per compatibilità con le versioni precedenti si prega di utilizzare la soluzione di @ BrenBarn.
Pavan Gupta

12
Deve sempre confrontare Nessuno con, "non è", invece di "! =". stackoverflow.com/a/14247419/2368836
rocktheartsm4l

21

Se hai davvero bisogno di modificare il dizionario originale:

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

Nota che dobbiamo fare un elenco delle chiavi vuote perché non possiamo modificare un dizionario mentre iteriamo attraverso di esso (come potresti aver notato). Tuttavia, è meno costoso (dal punto di vista della memoria) rispetto alla creazione di un dizionario nuovo di zecca, a meno che non ci siano molte voci con valori vuoti.


questo rimuoverà anche il valore 0 e 0 non è vuoto
JVK

2
Se stai usando Python 3+ devi sostituire .iteritems()con .items(), il primo non funziona più nelle ultime versioni di Python.
Mariano Ruiz


12

Se si desidera un approccio completo ma conciso alla gestione delle strutture di dati del mondo reale che sono spesso annidate e possono persino contenere cicli, consiglio di guardare l'utilità di rimappatura dal pacchetto di utilità boltons .

Dopo pip install boltonso aver copiato iterutils.py nel tuo progetto, fai semplicemente:

from boltons.iterutils import remap

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

Questa pagina contiene molti altri esempi, inclusi quelli che lavorano con oggetti molto più grandi dall'API di Github.

È puro Python, quindi funziona ovunque ed è completamente testato in Python 2.7 e 3.3+. Soprattutto, l'ho scritto esattamente per casi come questo, quindi se trovi un caso che non gestisce, puoi infastidirmi per risolverlo proprio qui .


1
Questa soluzione ha funzionato alla grande per un problema simile che ho avuto: eliminare i valori vuoti da elenchi profondamente annidati all'interno dei dizionari. Grazie!
Nicholas Tulach

1
Questo è positivo, poiché non stai reinventando la ruota e fornendo una soluzione per gli oggetti annidati. Grazie!
vekerdyb

1
Mi è piaciuto molto l'articolo che hai scritto per la tua libreria, e questa è una libreria utile!
lifelogger

11

In base alla soluzione di Ryan , se hai anche elenchi e dizionari nidificati:

Per 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

Per 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
Ah, bella estensione! È una buona soluzione per dizionari come i seguenti:d = { "things": [{ "name": "" }] }
Ryan Shea

6

Se hai un dizionario annidato e vuoi che funzioni anche per sottoelementi vuoti, puoi usare una variante ricorsiva del suggerimento di 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

Utilizzare items()invece di iteritems()per Python 3
andydavies

6

Risposta rapida (TL; DR)

Example01

### 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'}
'''

Risposta dettagliata

Problema

  • Contesto: Python 2.x
  • Scenario: lo sviluppatore desidera modificare un dizionario per escludere valori vuoti
    • aka rimuove i valori vuoti da un dizionario
    • aka elimina le chiavi con valori vuoti
    • aka dizionario di filtro per valori non vuoti su ciascuna coppia chiave-valore

Soluzione

  • esempio01 usa la sintassi di comprensione delle liste di Python con un semplice condizionale per rimuovere i valori "vuoti"

insidie

  • example01 opera solo su una copia del dizionario originale (non modifica in posizione)
  • example01 può produrre risultati imprevisti a seconda di cosa lo sviluppatore intende per "vuoto"
    • Lo sviluppatore intende mantenere valori falsi ?
    • Se i valori nel dizionario non sono garantiti come stringhe, lo sviluppatore potrebbe subire una perdita di dati imprevista.
    • result01 mostra che solo tre coppie chiave-valore sono state preservate dal set originale

Esempio alternativo

  • example02 aiuta ad affrontare potenziali insidie
  • L'approccio consiste nell'usare una definizione più precisa di "vuoto" modificando il condizionale.
  • Qui vogliamo solo filtrare i valori che restituiscono stringhe vuote.
  • Qui usiamo anche .strip () per filtrare i valori che consistono solo di spazi bianchi.

Example02

### 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'
  }
'''

Guarda anche



4

Basandosi sulle risposte di patriciasz e nneonneo , e tenendo conto della possibilità che tu possa voler eliminare chiavi che hanno solo certe cose false (es '') ma non altre (es 0), o forse vuoi anche includere alcune cose vere (es 'SPAM') , quindi potresti creare una lista di risultati altamente specifica:

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

Sfortunatamente, questo non funziona del tutto, perché ad esempio 0 in unwantedrestituisce True. Dobbiamo discriminare tra 0e altre cose false, quindi dobbiamo usare is:

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

... restituisce False.

Ora usalo per delle cose indesiderate:

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]

Se vuoi un nuovo dizionario, invece di modificarlo metadatain posizione:

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

scatto davvero bello, affronta molti problemi contemporaneamente e risolve la domanda, grazie per averlo chiarito
jlandercy

Freddo! Funziona per questo esempio. Tuttavia, non funziona quando un elemento nel dizionario è[]
jsga

2

Ho letto tutte le risposte in questo thread e alcune si riferivano anche a questo thread: Rimuovi i dict vuoti nel dizionario annidato con la funzione ricorsiva

Inizialmente ho usato la soluzione qui e ha funzionato benissimo:

Tentativo 1: troppo caldo (non performante o a prova di futuro) :

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

Ma alcuni problemi di prestazioni e compatibilità sono stati sollevati nel mondo di Python 2.7:

  1. usa isinstanceinvece ditype
  2. srotolare l'elenco comp in forloop per l'efficienza
  3. usa python3 safe itemsinvece diiteritems

Tentativo 2: troppo freddo (manca la memorizzazione) :

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! Questo non è ricorsivo e per niente memoizant.

Tentativo 3: giusto (finora) :

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
a meno che non sia cieco, mi sembra che i tentativi 2 e 3 siano esattamente gli stessi ...
luckyguy73

1

Dict mescolati con array

  • La risposta al tentativo 3: Just Right (finora) dalla risposta di BlissRage non gestisce correttamente gli elementi degli array. Includo una patch nel caso qualcuno ne abbia bisogno. Il metodo è gestisce l'elenco con il blocco di istruzioni di if isinstance(v, list):, che cancella l'elenco utilizzando l' scrub_dict(d)implementazione originale .
    @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

eccezionale . . .
Avevo

0

Un modo alternativo per farlo è usare la comprensione del dizionario. Questo dovrebbe essere compatibile con2.7+

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

0

Ecco un'opzione se stai usando 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

Alcuni dei metodi sopra menzionati ignorano se sono presenti numeri interi e float con valori 0 e 0,0

Se qualcuno vuole evitare quanto sopra può utilizzare il codice seguente (rimuove le stringhe vuote e i valori Nessuno dal dizionario annidato e dall'elenco annidato):

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

"Dato che attualmente scrivo anche un'applicazione desktop per il mio lavoro con Python, ho trovato nell'applicazione di immissione dati quando ci sono molte voci e alcune non sono obbligatorie, quindi l'utente può lasciarlo vuoto, a scopo di convalida, è facile da afferrare tutte le voci e quindi scartare la chiave o il valore vuoto di un dizionario. Quindi il mio codice sopra mostra come possiamo facilmente eliminarli, usando la comprensione del dizionario e mantenere l'elemento del valore del dizionario che non è vuoto.Uso 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'}

Si prega di menzionare la versione python supporterà anche l'ultima versione?
HaseeB Mir

La tua risposta è attualmente contrassegnata poiché la bassa qualità potrebbe essere eliminata. Assicurati che la tua risposta contenga una spiegazione oltre a qualsiasi codice.
Tim Stack

@TimStack Si prega di consigliare l'eliminazione per le risposte LQ.
10 Rep,

@ 10Rep Non consiglierò la cancellazione per una risposta che potrebbe funzionare come una soluzione ma è semplicemente priva di commenti descrittivi. Preferisco informare l'utente e insegnargli come si presenta una risposta migliore.
Tim Stack

@HasseB Mir Uso l'ultimo Python 3.8.3
KokoEfraim

-2

Alcuni benchmark:

1. Comprensione elenco ricreare 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. Comprensione delle liste ricreare dict usando 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. Ripeti il ​​ciclo e cancella la chiave se v è Nessuno

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

quindi loop and delete è il più veloce a 160ns, la comprensione dell'elenco è la metà lenta a ~ 375ns e con una chiamata a dict()è di nuovo la metà lenta ~ 680ns.

Mettere 3 in una funzione lo riporta di nuovo a circa 275 ns. Anche per me PyPy era circa il doppio più veloce di Neet Python.


Il ciclo e l'eliminazione possono anche generare un'eccezione RunTimeError, poiché non è valido modificare un dizionario durante l'iterazione di una vista. docs.python.org/3/library/stdtypes.html s4.10.1
Airsource Ltd

ah amico sì ok in python 3 questo è vero ma non in python 2.7 dato che gli elementi restituiscono una lista, quindi devi chiamare list(dic.items())in py 3. Comprensione dei dettami ftw allora? del sembra ancora più veloce per un basso rapporto di valori Null / vuoti. Immagino che costruire quell'elenco sia altrettanto dannoso per il consumo di memoria che ricreare semplicemente il dict.
Richard Mathie
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.