¿Cómo verifico que varias claves están en un dict en una sola pasada?

218

Quiero hacer algo como:

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

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

¿Cómo verifico si tanto 'foo' como 'bar' están en dict foo?

Jean-François Corbett
fuente

Respuestas:

363

Bueno, podrías hacer esto:

>>> if all (k in foo for k in ("foo","bar")):
...     print "They're there!"
...
They're there!
hughdbrown
fuente
10
+1, me gusta más que la respuesta de Greg porque es más conciso Y más rápido (no se crea una lista temporal irrelevante Y se aprovecha al máximo el cortocircuito).
Alex Martelli
44
Me encantan todos () y cualquiera (). Hacen que muchos algoritmos sean mucho más limpios.
hughdbrown
Finalmente terminé usando esta solución. Parecía lo mejor para conjuntos de datos más grandes. Al verificar por decir 25 o 30 claves.
44
Es una buena solución gracias al cortocircuito, especialmente si la prueba falla la mayoría de las veces; a menos que pueda crear el conjunto de claves de interés solo una vez y verificarlo muchas veces, en cuyo caso setes superior. Como de costumbre ... ¡mídelo! -)
Alex Martelli
Lo uso cada vez que se ve mejor que la forma "normal", con todos los and's o the or's ... también es bueno porque puedes usar "all" o "any" ... además puedes tener " k in foo "o" k not in foo "según la prueba que intente realizar
Terence Honles
123
if {"foo", "bar"} <= myDict.keys(): ...

Si todavía estás en Python 2, puedes hacer

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

Si todavía está en un Python <= 2.6 muy antiguo, puede invocar setel dict, pero iterará sobre todo el dict para construir el conjunto, y eso es lento:

if set(("foo", "bar")) <= set(myDict): ...
Alex Martelli
fuente
¡se ve bien! Lo único que no me gusta es que tienes que crear conjuntos temporales, pero es muy compacto. Entonces debo decir ... ¡buen uso de sets!
Terence Honles
17
En python 3 puede decir set(("foo","bar")) <= myDict.keys()qué evita el conjunto temporal, por lo que es mucho más rápido. Para mi prueba, tiene aproximadamente la misma velocidad que usar todo cuando la consulta tenía 10 elementos. Sin embargo, se vuelve más lento a medida que la consulta se hace más grande.
John La Rooy
1
He publicado algunas de mis pruebas como respuesta. stackoverflow.com/questions/1285911/…
John La Rooy
30
if {'foo', 'bar'} <= set(myDict): ...
Boris Raicheff
11
Para cualquiera que se pregunte por qué esto funciona: el operador <= es el mismo que usar el método .set issubset (): docs.python.org/3/library/stdtypes.html#set-types-set-frozenset
edepe
41

Plataforma de evaluación comparativa simple para 3 de las alternativas.

Ponga sus propios valores para D y 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
John La Rooy
fuente
44
Python 2.7 tiene d.viewkeys()que hacer set(q) <= d.viewkeys().
Martijn Pieters
Python 2.7.5Tiene d.keys()método también.
Ivan Kharlamov
3
@IvanKharlamov, pero en Python2, no devuelve un objeto que sea compatible conset(q) <= ...
John La Rooy
1
Lo malo es que estás en lo cierto: vuelve TypeError: can only compare to a set. ¡Lo siento! :))
Ivan Kharlamov
1
Para Python 2 cambiar el orden: d.viewkeys() >= set(q). ¡Vine aquí tratando de averiguar por qué importa el orden!
Veedrac
34

No tiene que envolver el lado izquierdo en un conjunto. Puedes hacer esto:

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

Esto también funciona mejor que la all(k in d...)solución.

Claytonk
fuente
2
Esto también funciona mejor que la solución all (k in d ...). Sugerí esto como una edición, pero fue rechazado porque era mejor agregar un comentario . Así que aquí estoy yo haciendo exactamente eso
miraculixx
@miraculixx No es mejor agregar un comentario. Es mejor editar información relevante en una respuesta y eliminar los comentarios.
endolito
1
@endolith Estoy de acuerdo, algunas personas obviamente no lo hacen, como puedes ver en la edición rechazada que hice en primer lugar. De todos modos, esa es una discusión para meta no por aquí.
miraculixx
¿Puede alguien explicar esto, por favor? He deducido que {} crea un conjunto, pero ¿cómo está trabajando aquí el operador de menor o igual?
Locane
1
@Locane El operador <= prueba si el primer conjunto es un subconjunto del segundo conjunto. También puede hacer {'foo', 'bar'}. Issubset (somedict). La documentación para la metodología establecida se puede encontrar aquí: docs.python.org/2/library/sets.html
Meow
24

Usando conjuntos :

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

Alternativamente:

if set(("foo", "bar")) <= set(foo):
    #do stuff
Karl Voigtland
fuente
2
set (d) como usé en mi respuesta es como set (d.keys ()) pero más rápido, más corto, y diría que es preferible estilísticamente.
Alex Martelli
set(d)es lo mismo que set(d.keys())(sin la lista intermedia que d.keys()construye)
Jochen Ritzel
11

Qué tal esto:

if all([key in foo for key in ["foo","bar"]]):
    # do stuff
    pass
Greg
fuente
8
de hecho, no solo innecesarios, positivamente dañinos, ya que impiden el comportamiento normal de cortocircuito de all.
Alex Martelli
10

Creo que este es el más inteligente y decisivo.

{'key1','key2'} <= my_dict.keys()
Shota Tamura
fuente
9

Si bien me gusta la respuesta de Alex Martelli, no me parece Pythonic. Es decir, pensé que una parte importante de ser Pythonic es ser fácilmente comprensible. Con ese objetivo, <=no es fácil de entender.

Si bien son más personajes, issubset()es más comprensible usar como lo sugiere la respuesta de Karl Voigtland. Dado que ese método puede usar un diccionario como argumento, una solución breve y comprensible es:

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

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

Me gustaría usar {'foo', 'bar'}en lugar de set(('foo', 'bar')), porque es más corto. Sin embargo, no es tan comprensible y creo que las llaves se confunden demasiado fácilmente como un diccionario.

LS
fuente
2
Creo que es comprensible una vez que entiendes lo que significa.
Bobort
Está en la documentación como sinónimo de .issubset(). Creo que estar en la documentación de Python lo hace Pythonic por defecto.
ingyhere
4

La solución de Alex Martelli set(queries) <= set(my_dict) es el código más corto pero puede no ser el más rápido. Suponga Q = len (consultas) y D = len (my_dict).

Esto toma O (Q) + O (D) para hacer los dos conjuntos, y luego (¡uno espera!) Solo O (min (Q, D)) para hacer la prueba de subconjunto, suponiendo, por supuesto, que la búsqueda de conjuntos de Python es O (1): este es el peor de los casos (cuando la respuesta es verdadera).

La solución generadora de hughdbrown (et al?) all(k in my_dict for k in queries)Es el peor de los casos O (Q).

Factores complicados:
(1) todos los bucles en el gadget basado en conjuntos se realizan a velocidad C, mientras que el gadget basado en cualquier se repite en código de bytes.
(2) La persona que llama del gadget basado en cualquier puede usar cualquier conocimiento de probabilidad de falla para ordenar los elementos de consulta en consecuencia, mientras que el gadget basado en conjunto no permite tal control.

Como siempre, si la velocidad es importante, la evaluación comparativa en condiciones operativas es una buena idea.

John Machin
fuente
1
El generador fue más rápido para todos los casos que probé. stackoverflow.com/questions/1285911/…
John La Rooy
2

Puede usar .issubset () también

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

¿Qué tal usar lambda?

 if reduce( (lambda x, y: x and foo.has_key(y) ), [ True, "foo", "bar"] ): # do stuff
Jinuk Kim
fuente
2
Esta respuesta es la única funcionalmente correcta que funcionará en Python 1.5 con un simple cambio (s / True / 1 /) ... pero no tiene nada más que hacer. Y la cosa verdadera sería mejor como el argumento inicializador opcional en lugar de estar metido en el frente de la secuencia arg.
John Machin
1

En caso de que quieras:

  • también obtener los valores para las claves
  • marque más de un dictonario

luego:

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
Jochen Ritzel
fuente
1

No para sugerir que esto no es algo en lo que no haya pensado, pero creo que lo más simple suele ser lo mejor:

if ("foo" in foo) and ("bar" in foo):
    # do stuff
Jason Baker
fuente
1
>>> if 'foo' in foo and 'bar' in foo:
...     print 'yes'
... 
yes

Jason, () no son necesarios en Python.

Juanjo Conti
fuente
3
Aún así, podrían ser de buen estilo ... sin ellos, mi cerebro confundido de C ++ siempre se pregunta si será interpretado como "si 'foo in (foo y' bar ') in foo:"
Jeremy Friesner el
1
Entiendo que no son necesarios. Simplemente siento que agregan claridad en este caso.
Jason Baker
0

Solo mi opinión sobre esto, hay dos métodos que son fáciles de entender de todas las opciones dadas. Entonces, mi criterio principal es tener un código muy legible, no un código excepcionalmente rápido. Para mantener el código comprensible, prefiero las posibilidades dadas:

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

El hecho de que "var <= var2.keys ()" se ejecuta más rápido en mis pruebas a continuación, prefiero este.

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
PietjePuk
fuente
0

En el caso de determinar si solo algunas teclas coinciden, esto funciona:

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

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

Otra opción más para encontrar si solo algunas teclas coinciden:

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

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

Otra opción para detectar si todas las claves están en 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
ingyhere
fuente
-4
>>> ok
{'five': '5', 'two': '2', 'one': '1'}

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

Esto parece funcionar

Prashanth Gowda
fuente
Esto es inteligente y estaba convencido de que no funcionó hasta que lo probé yo mismo. Sospeché la ()serían evaluados primero y dar como resultado True, que luego comprobar si True in ok. ¿Cómo funciona esto realmente?
durden2.0
77
('dos' y 'uno' y 'cinco') devuelve 'cinco', por lo que en realidad solo verifica si 'cinco' está en el dict
HardQuestions