Come posso verificare che più chiavi siano in un dict in un unico passaggio?


218

Voglio fare qualcosa del tipo:

foo = {'foo':1,'zip':2,'zam':3,'bar':4}

if ("foo","bar") in foo:
    #do stuff

Come posso verificare se sia "foo" che "bar" sono in dict foo?

Risposte:


363

Bene, potresti farlo:

>>> if all (k in foo for k in ("foo","bar")):
...     print "They're there!"
...
They're there!

10
+1, mi piace meglio della risposta di Greg perché è più conciso E più veloce (nessuna costruzione di elenco temporaneo irrilevante E pieno sfruttamento del corto circuito).
Alex Martelli,

4
Adoro tutti () e any (). Rendono così tanti algoritmi molto più puliti.
hughdbrown,

Alla fine ho finito per usare questa soluzione. Sembrava il migliore per set di dati più grandi. Quando si controlla per dire 25 o 30 tasti.

4
È una buona soluzione grazie al corto circuito, soprattutto se il test fallisce più spesso; a meno che non sia possibile creare il set di chiavi di interesse una sola volta e verificarlo più volte, nel qual caso setè superiore. Come al solito ... misuralo! -)
Alex Martelli il

Lo uso ogni volta che sembra più bello del modo "normale", con tutti gli e o gli o ... è anche bello perché puoi usare "tutto" o "qualsiasi" ... inoltre puoi avere " k in foo "o" k not in foo "a seconda del test che si sta tentando di eseguire
Terence Honles

123
if {"foo", "bar"} <= myDict.keys(): ...

Se sei ancora su Python 2, puoi farlo

if {"foo", "bar"} <= myDict.viewkeys(): ...

Se sei ancora su un Python molto vecchio <= 2.6, puoi chiamare setil dict, ma itererà su tutto il dict per costruire il set, e questo è lento:

if set(("foo", "bar")) <= set(myDict): ...

sembra buono! L'unica cosa che non mi piace è che devi creare set temporanei, ma è molto compatto. Quindi devo dire ... buon uso dei set!
Terence Honles,

17
In python 3 puoi dire set(("foo","bar")) <= myDict.keys()che evita il set temporaneo, quindi è molto più veloce. Per i miei test ha circa la stessa velocità dell'utilizzo di tutto quando la query era di 10 elementi. Diventa più lento man mano che la query diventa più grande.
John La Rooy,

1
Ho pubblicato alcuni dei miei test come risposta. stackoverflow.com/questions/1285911/…
John La Rooy,

30
if {'foo', 'bar'} <= set(myDict): ...
Boris Raicheff,

11
Per chiunque si chieda perché funzioni: l'operatore <= è uguale al metodo use
.set

41

Piattaforma di benchmarking semplice per 3 delle alternative.

Inserisci i tuoi valori per D e Q


>>> from timeit import Timer
>>> setup='''from random import randint as R;d=dict((str(R(0,1000000)),R(0,1000000)) for i in range(D));q=dict((str(R(0,1000000)),R(0,1000000)) for i in range(Q));print("looking for %s items in %s"%(len(q),len(d)))'''

>>> Timer('set(q) <= set(d)','D=1000000;Q=100;'+setup).timeit(1)
looking for 100 items in 632499
0.28672504425048828

#This one only works for Python3
>>> Timer('set(q) <= d.keys()','D=1000000;Q=100;'+setup).timeit(1)
looking for 100 items in 632084
2.5987625122070312e-05

>>> Timer('all(k in d for k in q)','D=1000000;Q=100;'+setup).timeit(1)
looking for 100 items in 632219
1.1920928955078125e-05

4
Python 2.7 deve d.viewkeys()fare set(q) <= d.viewkeys().
Martijn Pieters

Python 2.7.5ha d.keys()anche metodo.
Ivan Kharlamov,

3
@IvanKharlamov, ma in Python2, non restituisce un oggetto compatibile conset(q) <= ...
John La Rooy

1
Mio male, sei assolutamente a posto: ritorna TypeError: can only compare to a set. Scusa! :))
Ivan Kharlamov il

1
Per Python 2 cambiare l'ordine: d.viewkeys() >= set(q). Sono venuto qui cercando di scoprire perché l'ordine conta!
Veedrac,

34

Non è necessario avvolgere il lato sinistro in un set. Puoi fare solo questo:

if {'foo', 'bar'} <= set(some_dict):
    pass

Anche questo funziona meglio della all(k in d...)soluzione.


2
Questo funziona anche meglio della soluzione all (k in d ...). Ho suggerito questo come una modifica, ma è stato respinto per motivi che era meglio aggiungere un commento . Quindi ecco che
sto

@miraculixx Non è meglio aggiungere un commento. È meglio modificare le informazioni pertinenti in una risposta ed eliminare i commenti.
endolith

1
@endolith Sono d'accordo, alcune persone ovviamente no, come puoi vedere nella modifica rifiutata che ho fatto in primo luogo. Comunque questa è una discussione per meta non per qui.
miraculixx,

Qualcuno può spiegare questo per favore? Ho capito che {} crea un set, ma come sta lavorando l'operatore inferiore o uguale?
Locane,

1
@Locane L'operatore <= verifica se il primo set è un sottoinsieme del secondo set. Puoi anche fare {'pippo', 'bar'}. Issubset (somedict). La documentazione per la metodologia impostata è disponibile qui: docs.python.org/2/library/sets.html
Meow,

24

Utilizzando set :

if set(("foo", "bar")).issubset(foo):
    #do stuff

In alternativa:

if set(("foo", "bar")) <= set(foo):
    #do stuff

2
set (d) come ho usato nella mia risposta è proprio come set (d.keys ()) ma più veloce, più corto e direi stilisticamente preferibile.
Alex Martelli,

set(d)è lo stesso di set(d.keys())(senza l'elenco intermedio che d.keys()costruisce)
Jochen Ritzel,

11

Cosa ne pensi di questo:

if all([key in foo for key in ["foo","bar"]]):
    # do stuff
    pass

8
anzi, non solo inutili, positivamente dannosi, in quanto impediscono il normale comportamento di corto circuito di all.
Alex Martelli,

10

Penso che questo sia il più intelligente e pitonico.

{'key1','key2'} <= my_dict.keys()

9

Mentre mi piace la risposta di Alex Martelli, non mi sembra Pythonic. Cioè, ho pensato che una parte importante dell'essere Pythonic fosse di essere facilmente comprensibile. Con questo obiettivo, <=non è facile da capire.

Sebbene siano più personaggi, l'utilizzo issubset()come suggerito dalla risposta di Karl Voigtland è più comprensibile. Poiché tale metodo può utilizzare un dizionario come argomento, una soluzione breve e comprensibile è:

foo = {'foo': 1, 'zip': 2, 'zam': 3, 'bar': 4}

if set(('foo', 'bar')).issubset(foo):
    #do stuff

Mi piacerebbe usare {'foo', 'bar'}al posto di set(('foo', 'bar')), perché è più corto. Tuttavia, non è così comprensibile e penso che le parentesi graffe siano troppo facilmente confuse come un dizionario.


2
Penso che sia comprensibile una volta capito cosa significa.
Bobort,

È nella documentazione come sinonimo di .issubset(). Penso che essere nella documentazione di Python lo rende Pythonic per impostazione predefinita.
ingyhere

4

La soluzione di Alex Martelli set(queries) <= set(my_dict)è il codice più breve ma potrebbe non essere il più veloce. Assumi Q = len (query) e D = len (my_dict).

Questo richiede O (Q) + O (D) per creare i due set e quindi (uno spera!) Solo O (min (Q, D)) per eseguire il test del sottoinsieme, supponendo ovviamente che Python abbia impostato la ricerca è O (1) - questo è il caso peggiore (quando la risposta è vera).

La soluzione del generatore di hughdbrown (et al?) all(k in my_dict for k in queries)È il caso peggiore O (Q).

Fattori complicanti:
(1) i loop nel gadget basato su set sono tutti eseguiti a velocità C mentre il gadget qualsiasi basato su loop passa sopra il bytecode.
(2) Il chiamante del gadget basato su qualsiasi potrebbe essere in grado di utilizzare qualsiasi conoscenza della probabilità di errore per ordinare gli elementi della query di conseguenza, mentre il gadget basato su set non consente tale controllo.

Come sempre, se la velocità è importante, l'analisi comparativa in condizioni operative è una buona idea.


1
Il generatore è stato più veloce per tutti i casi che ho provato. stackoverflow.com/questions/1285911/…
John La Rooy,

2

È possibile utilizzare .issubset () come pure

>>> {"key1", "key2"}.issubset({"key1":1, "key2":2, "key3": 3})
True
>>> {"key4", "key2"}.issubset({"key1":1, "key2":2, "key3": 3})
False
>>>

1

Che ne dici di usare lambda?

 if reduce( (lambda x, y: x and foo.has_key(y) ), [ True, "foo", "bar"] ): # do stuff

2
Questa risposta è l'unica funzionalmente corretta che funzionerà su Python 1.5 con una semplice modifica (s / True / 1 /) ... ma non ha altro da fare. E la cosa vera sarebbe meglio come arg inizializzatore opzionale piuttosto che stipato nella parte anteriore della sequenza arg.
John Machin,

1

Nel caso tu voglia:

  • ottieni anche i valori per le chiavi
  • controlla più di un dittonary

poi:

from operator import itemgetter
foo = {'foo':1,'zip':2,'zam':3,'bar':4}
keys = ("foo","bar") 
getter = itemgetter(*keys) # returns all values
try:
    values = getter(foo)
except KeyError:
    # not both keys exist
    pass

1

Non per suggerire che questo non è qualcosa a cui non hai pensato, ma trovo che la cosa più semplice sia di solito la migliore:

if ("foo" in foo) and ("bar" in foo):
    # do stuff

1
>>> if 'foo' in foo and 'bar' in foo:
...     print 'yes'
... 
yes

Jason, () non sono necessari in Python.


3
Tuttavia potrebbero essere di buon stile ... senza di loro, il mio cervello C ++ - addled si chiede sempre se verrà interpretato come "if 'foo in (foo and' bar ') in foo:"
Jeremy Friesner il

1
Capisco che non sono necessari. Sento solo che aggiungono chiarezza in questo caso.
Jason Baker,

0

Solo la mia opinione su questo, ci sono due metodi che sono facili da capire di tutte le opzioni fornite. Quindi il mio criterio principale è avere un codice molto leggibile, non un codice eccezionalmente veloce. Per mantenere comprensibile il codice, preferisco dare delle possibilità:

  • var <= var2.keys ()
  • var.issubset (var2)

Il fatto che "var <= var2.keys ()" venga eseguito più velocemente nei miei test di seguito, preferisco questo.

import timeit

timeit.timeit('var <= var2.keys()', setup='var={"managed_ip", "hostname", "fqdn"}; var2= {"zone": "test-domain1.var23.com", "hostname": "bakje", "api_client_ip": "127.0.0.1", "request_data": "", "request_method": "GET", "request_url": "hvar2p://127.0.0.1:5000/test-domain1.var23.com/bakje", "utc_datetime": "04-Apr-2019 07:01:10", "fqdn": "bakje.test-domain1.var23.com"}; var={"managed_ip", "hostname", "fqdn"}')
0.1745898080000643

timeit.timeit('var.issubset(var2)', setup='var={"managed_ip", "hostname", "fqdn"}; var2= {"zone": "test-domain1.var23.com", "hostname": "bakje", "api_client_ip": "127.0.0.1", "request_data": "", "request_method": "GET", "request_url": "hvar2p://127.0.0.1:5000/test-domain1.var23.com/bakje", "utc_datetime": "04-Apr-2019 07:01:10", "fqdn": "bakje.test-domain1.var23.com"}; var={"managed_ip", "hostname", "fqdn"};')
0.2644960229999924

0

Nel caso di determinare se solo alcune chiavi corrispondono, questo funziona:

any_keys_i_seek = ["key1", "key2", "key3"]

if set(my_dict).intersection(any_keys_i_seek):
    # code_here
    pass

Ancora un'altra opzione per trovare se solo alcune chiavi corrispondono:

any_keys_i_seek = ["key1", "key2", "key3"]

if any_keys_i_seek & my_dict.keys():
    # code_here
    pass

0

Un'altra opzione per rilevare se tutti i tasti sono in un dict:

dict_to_test = { ... }  # dict
keys_sought = { "key_sought_1", "key_sought_2", "key_sought_3" }  # set

if keys_sought & dict_to_test.keys() == keys_sought: 
    # yes -- dict_to_test contains all keys in keys_sought
    # code_here
    pass

-4
>>> ok
{'five': '5', 'two': '2', 'one': '1'}

>>> if ('two' and 'one' and 'five') in ok:
...   print "cool"
... 
cool

Questo sembra funzionare


Questo è intelligente e ero convinto che non funzionasse fino a quando non l'ho provato io stesso. Sospettavo che ()sarebbe stato valutato per primo e si Truesarebbe verificato, il che avrebbe verificato se True in ok. Come funziona davvero ?!
durden2.0

7
('due' e 'uno' e 'cinque') restituisce 'cinque', quindi in realtà controlla solo se 'cinque' è sul dict
HardQuestions
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.