Filtra dict per contenere solo determinate chiavi?


497

Ho un dictche ha un sacco di voci. Sono interessato solo ad alcuni selezionati. C'è un modo semplice per eliminare tutti gli altri?


È utile dire quale tipo di chiavi (numeri interi? Stringhe? Date? Oggetti arbitrari?) E quindi se esiste un semplice test (stringa, regex, appartenenza alla lista o disuguaglianza numerica) per verificare quali chiavi sono dentro o fuori. Altrimenti dobbiamo chiamare una o più funzioni arbitrarie per determinarlo.
smci,

@smci String keys. Non penso nemmeno che mi sia venuto in mente che avrei potuto usare qualsiasi altra cosa; Sto
scrivendo

Risposte:


656

Costruire un nuovo dict:

dict_you_want = { your_key: old_dict[your_key] for your_key in your_keys }

Usa la comprensione del dizionario.

Se usi una versione che le manca (cioè Python 2.6 e precedenti), fallo dict((your_key, old_dict[your_key]) for ...). È lo stesso, anche se più brutto.

Nota che questo, a differenza della versione di jnnnnn, ha prestazioni stabili (dipende solo dal numero di your_keys) per le old_dicts di qualsiasi dimensione. Sia in termini di velocità che di memoria. Poiché si tratta di un'espressione del generatore, elabora un elemento alla volta e non esamina tutti gli elementi di old_dict.

Rimozione di tutto sul posto:

unwanted = set(keys) - set(your_dict)
for unwanted_key in unwanted: del your_dict[unwanted_key]

8
"Usa la comprensione del dizionario, se usi una versione che le manca" == versione <= 2.6
getekha,

8
Genera un KeyError se una delle chiavi del filer non è presente in old_dict. Suggerirei {k: d [k] per k nel filtro se k in d}
Peter Gibson,

1
@PeterGibson Sì, se fa parte dei requisiti, devi fare qualcosa al riguardo. Che stia facendo cadere silenziosamente le chiavi, aggiungendo un valore predefinito o qualcos'altro, dipende da cosa stai facendo; ci sono molti casi d'uso in cui il tuo approccio è sbagliato. Ci sono anche molti in cui una chiave mancante old_dictindica un bug altrove, e in quel caso preferisco di gran lunga un errore a risultati silenziosamente errati.

@delnan, anche l'aggiunta "if k in d" ti rallenta se d è grande, ho solo pensato che valesse la pena menzionarlo
Peter Gibson

7
@PeterGibson No, la ricerca nel dizionario è O (1).

130

Comprensione del dict leggermente più elegante:

foodict = {k: v for k, v in mydict.items() if k.startswith('foo')}

Upvoted. Stavo pensando di aggiungere una risposta simile a questa. Solo per curiosità, perché {k: v per k, v in dict.items () ...} anziché {k: dict [k] per k in dict ...} C'è una differenza di prestazioni?
Hart Simha,

4
Ha risposto alla mia domanda. {K: dict [k] per k in dict ...} è circa il 20-25% più veloce, almeno in Python 2.7.6, con un dizionario di 26 elementi (timeit (..., setup = "d = {chr (x + 97): x + 1 per x in range (26)} ")), a seconda di quanti elementi vengono filtrati (filtrare le chiavi consonanti è più veloce del filtrare le chiavi vocali perché stai guardando meno articoli). La differenza di prestazioni potrebbe benissimo diventare meno significativa con l'aumentare delle dimensioni del dizionario.
Hart Simha,

5
Probabilmente sarebbe lo stesso perf se mydict.iteritems()invece lo usassi. .items()crea un altro elenco.
Pat

64

Ecco un esempio in Python 2.6:

>>> a = {1:1, 2:2, 3:3}
>>> dict((key,value) for key, value in a.iteritems() if key == 1)
{1: 1}

La parte di filtro è l' ifistruzione.

Questo metodo è più lento della risposta di Delnan se si desidera selezionare solo alcuni dei moltissimi tasti.


11
tranne che probabilmente userei if key in ('x','y','z')immagino.
Aprire il

se sai già quali chiavi vuoi, usa la risposta di delnan. Se è necessario testare ciascuna chiave con un'istruzione if, utilizzare la risposta di ransford.
jnnnnn,

1
Questa soluzione ha un ulteriore vantaggio. Se il dizionario viene restituito da una costosa chiamata di funzione (ovvero a / old_dict è una chiamata di funzione) questa soluzione chiama la funzione una sola volta. In un ambiente imperativo la memorizzazione del dizionario restituito dalla funzione in una variabile non è un grosso problema ma in un ambiente funzionale (ad esempio in un lambda) questa è l'osservazione chiave.
gae123,


20

Codice 1:

dict = { key: key * 10 for key in range(0, 100) }
d1 = {}
for key, value in dict.items():
    if key % 2 == 0:
        d1[key] = value

Codice 2:

dict = { key: key * 10 for key in range(0, 100) }
d2 = {key: value for key, value in dict.items() if key % 2 == 0}

Codice 3:

dict = { key: key * 10 for key in range(0, 100) }
d3 = { key: dict[key] for key in dict.keys() if key % 2 == 0}

Tutte le prestazioni del codice sono misurate con timeit usando il numero = 1000 e raccolte 1000 volte per ogni pezzo di codice.

inserisci qui la descrizione dell'immagine

Per python 3.6 le prestazioni di tre modi di filtrare le chiavi di comando sono quasi le stesse. Per python 2.7 il codice 3 è leggermente più veloce.


solo curioso, hai fatto quella trama da Python?
user5359531

1
ggplot2 in R - parte di tidyverse
keithpjolley il

18

Questa liner lambda dovrebbe funzionare:

dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])

Ecco un esempio:

my_dict = {"a":1,"b":2,"c":3,"d":4}
wanted_keys = ("c","d")

# run it
In [10]: dictfilt(my_dict, wanted_keys)
Out[10]: {'c': 3, 'd': 4}

È una comprensione di base dell'elenco che scorre sulle tue chiavi dict (i in x) e genera un elenco di coppie tuple (chiave, valore) se la chiave risiede nell'elenco delle chiavi desiderato (y). Un dict () racchiude il tutto in output come oggetto dict.


Dovrebbe usare un setper wanted_keys, ma per il resto sembra buono.
mpen

Questo mi dà un dizionario vuoto se il mio dizionario originale contiene elenchi al posto di valori. Qualche soluzione alternativa?
FaCoffee,

@Francesco, puoi fornire un esempio? Se corro dictfilt({'x':['wefwef',52],'y':['iuefiuef','efefij'],'z':['oiejf','iejf']}, ('x','z')):, ritorna {'x': ['wefwef', 52], 'z': ['oiejf', 'iejf']}come previsto.
Jim,

Ci ho provato con: dict={'0':[1,3], '1':[0,2,4], '2':[1,4]}e il risultato è stato {}, che ho assunto come un dict in bianco.
FaCoffee,

Una cosa, "dict" è una parola riservata, quindi non dovresti usarla per nominare un dict. Quali erano le chiavi che stavi cercando di estrarre? Se corro foo = {'0':[1,3], '1':[0,2,4], '2':[1,4]}; dictfilt(foo,('0','2')):, ottengo: {'0': [1, 3], '2': [1, 4]}qual è il risultato previsto
Jim,

14

Dato il dizionario originale orige il set di voci che ti interessano keys:

filtered = dict(zip(keys, [orig[k] for k in keys]))

che non è bello come la risposta di Delnan, ma dovrebbe funzionare in ogni versione di Python di interesse. È, tuttavia, fragile per ogni elemento dikeys esistente nel dizionario originale.


Bene, questa è fondamentalmente una versione desiderosa della "versione del generatore di tuple" della mia comprensione del dict. Davvero molto compatibile, sebbene le espressioni del generatore siano state introdotte nella 2.4, primavera del 2005 - sul serio, qualcuno lo sta ancora usando?

1
Non sono in disaccordo; 2.3 davvero non dovrebbe esistere più. Tuttavia, come un sondaggio obsoleto sull'uso della 2.3: moinmo.in/PollAboutRequiringPython24 Versione corta: RHEL4, SLES9, spedito con OS X 10.4
Kai

7

Basato sulla risposta accettata di delnan.

E se una delle tue chiavi desiderate non fosse nel vecchio_dict? La soluzione delnan genererà un'eccezione KeyError che puoi rilevare. Se non è quello che ti serve, forse vuoi:

  1. includi solo le chiavi che eccitano sia in old_dict sia nel tuo set di wanted_keys.

    old_dict = {'name':"Foobar", 'baz':42}
    wanted_keys = ['name', 'age']
    new_dict = {k: old_dict[k] for k in set(wanted_keys) & set(old_dict.keys())}
    
    >>> new_dict
    {'name': 'Foobar'}
  2. avere un valore predefinito per le chiavi che non è impostato in old_dict.

    default = None
    new_dict = {k: old_dict[k] if k in old_dict else default for k in wanted_keys}
    
    >>> new_dict
    {'age': None, 'name': 'Foobar'}

Potresti anche fare{k: old_dict.get(k, default) for k in ...}
Moberg

6

Questa funzione farà il trucco:

def include_keys(dictionary, keys):
    """Filters a dict by only including certain keys."""
    key_set = set(keys) & set(dictionary.keys())
    return {key: dictionary[key] for key in key_set}

Proprio come la versione di Delnan, questa utilizza la comprensione del dizionario e ha prestazioni stabili per dizionari di grandi dimensioni (dipende solo dal numero di chiavi consentite e non dal numero totale di chiavi nel dizionario).

E proprio come la versione di MyGGan, questa consente al tuo elenco di chiavi di includere chiavi che potrebbero non esistere nel dizionario.

E come bonus, ecco l'inverso, dove puoi creare un dizionario escludendo alcune chiavi nell'originale:

def exclude_keys(dictionary, keys):
    """Filters a dict by excluding certain keys."""
    key_set = set(dictionary.keys()) - set(keys)
    return {key: dictionary[key] for key in key_set}

Notare che a differenza della versione di Delnan, l'operazione non viene eseguita sul posto, quindi le prestazioni sono correlate al numero di chiavi nel dizionario. Tuttavia, il vantaggio è che la funzione non modificherà il dizionario fornito.

Modifica: aggiunta una funzione separata per escludere determinati tasti da un dict.


Dovresti consentire keysa qualsiasi tipo di iterabile, come quello che accetta il set .
Aprire il

Ah, buona chiamata, grazie per averlo segnalato. Farò quell'aggiornamento.
Ryan,

Mi chiedo se stai meglio con due funzioni. Se chiedessi a 10 persone " invertimplica che l' keysargomento è mantenuto o che l' keysargomento è respinto?", Quanti di loro sarebbero d'accordo?
skatenerd

Aggiornato. Fatemi sapere cosa ne pensate.
Ryan,

Questo sembra non funzionare se l'input dict ha delle liste al posto dei valori. In questo caso otterrai un vuoto. Qualche soluzione alternativa?
FaCoffee,

4

Se vogliamo creare un nuovo dizionario con le chiavi selezionate rimosse, possiamo fare uso della comprensione del dizionario
Ad esempio:

d = {
'a' : 1,
'b' : 2,
'c' : 3
}
x = {key:d[key] for key in d.keys() - {'c', 'e'}} # Python 3
y = {key:d[key] for key in set(d.keys()) - {'c', 'e'}} # Python 2.*
# x is {'a': 1, 'b': 2}
# y is {'a': 1, 'b': 2}

Neat. Funziona solo in Python 3. Python 2 dice "TypeError: tipi di operando non supportati per -: 'list' e 'set'"
mpen

Aggiunto set (d.keys ()) per Python 2. Funziona quando corro.
Srivastava,

2

Un'altra opzione:

content = dict(k1='foo', k2='nope', k3='bar')
selection = ['k1', 'k3']
filtered = filter(lambda i: i[0] in selection, content.items())

Ma ricevi un list(Python 2) o un iteratore (Python 3) restituito filter(), non un dict.


Avvolgere filteredin dicte si ottiene indietro il dizionario!
CMCDragonkai,

1

Forma breve:

[s.pop(k) for k in list(s.keys()) if k not in keep]

Come la maggior parte delle risposte suggerisce per mantenere la concisione, dobbiamo creare un oggetto duplicato sia esso un listo dict. Questo crea un lancio listma elimina le chiavi in ​​originale dict.


0

Ecco un altro metodo semplice che utilizza delin un liner:

for key in e_keys: del your_dict[key]

e_keysè l'elenco delle chiavi da escludere. Aggiornerà il tuo dict piuttosto che dartene uno nuovo.

Se vuoi un nuovo dict di output, crea una copia del dict prima di eliminare:

new_dict = your_dict.copy()           #Making copy of dict

for key in e_keys: del new_dict[key]

0

È possibile utilizzare python-benedict, è una sottoclasse dict.

Installazione: pip install python-benedict

from benedict import benedict

dict_you_want = benedict(your_dict).subset(keys=['firstname', 'lastname', 'email'])

È open-source su GitHub: https://github.com/fabiocaccamo/python-benedict


Disclaimer: sono l'autore di questa biblioteca.

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.