Como verifico se várias chaves estão em um ditado em uma única passagem?

218

Eu quero fazer algo como:

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

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

Como verifico se 'foo' e 'bar' estão no dict foo?

Jean-François Corbett
fonte

Respostas:

363

Bem, você poderia fazer isso:

>>> if all (k in foo for k in ("foo","bar")):
...     print "They're there!"
...
They're there!
hughdbrown
fonte
10
+1, eu gosto disso melhor do que a resposta de Greg, porque é mais conciso E mais rápido (sem criação de lista temporária irrelevante, E exploração total de curtos-circuitos).
Alex Martelli
4
Eu amo tudo () e qualquer (). Eles tornam muitos algoritmos muito mais limpos.
hughdbrown
Acabei usando essa solução. Parecia o melhor para conjuntos de dados maiores. Ao verificar, digamos, 25 ou 30 teclas.
4
É uma boa solução graças ao curto-circuito, especialmente se o teste falhar com mais frequência; a menos que você possa criar o conjunto de chaves de interesse apenas uma vez e verificá-lo várias vezes; nesse caso, seté superior. Como de costume ... medi-lo!)
Alex Martelli
Eu uso isso sempre que parece melhor do que o jeito "normal", com todos os and's ou the or's ... também é bom porque você pode usar "all" ou "any" ... além disso, pode ter " k em foo" ou 'não k em foo', dependendo do teste que você está tentando realizar
Terence Honles
123
if {"foo", "bar"} <= myDict.keys(): ...

Se você ainda está no Python 2, pode fazer

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

Se você ainda está em um Python realmente antigo <= 2.6, pode chamar seto dict, mas ele irá percorrer todo o dict para criar o conjunto, e isso é lento:

if set(("foo", "bar")) <= set(myDict): ...
Alex Martelli
fonte
parece bom! A única coisa que eu não gosto é que você precisa criar conjuntos temporários, mas é muito compacto. Então, devo dizer ... bom uso de conjuntos!
Terence Honles
17
No python 3, você pode dizer o set(("foo","bar")) <= myDict.keys()que evita o conjunto temporário, por isso é muito mais rápido. Para os meus testes, é quase a mesma velocidade que usar todos quando a consulta tinha 10 itens. Porém, fica mais lento à medida que a consulta aumenta.
31411 John La Rooy
1
Publiquei alguns dos meus testes como resposta. stackoverflow.com/questions/1285911/…
John La Rooy
30
if {'foo', 'bar'} <= set(myDict): ...
Boris Raicheff
11
Para quem se perguntar por que isso funciona: o operador <= é o mesmo que o método use .set issubset (): docs.python.org/3/library/stdtypes.html#set-types-set-frozenset
edepe
41

Equipamento de benchmarking simples para 3 das alternativas.

Coloque seus próprios valores para 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
John La Rooy
fonte
4
Python 2.7 tem d.viewkeys()que fazer set(q) <= d.viewkeys().
Martijn Pieters
Python 2.7.5tem d.keys()método também.
Ivan Kharlamov
3
@IvanKharlamov, mas em python2, ele não retorna um objeto que é compatível com oset(q) <= ...
John La Rooy
1
Meu mal, você está absolutamente no local: retorna TypeError: can only compare to a set. Desculpe! :))
Ivan Kharlamov
1
Para Python 2 mudar a ordem: d.viewkeys() >= set(q). Eu vim aqui tentando descobrir por que o pedido é importante!
Veedrac #
34

Você não precisa envolver o lado esquerdo em um conjunto. Você pode fazer isso:

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

Isso também tem um desempenho melhor que a all(k in d...)solução.

claytonk
fonte
2
Isso também tem um desempenho melhor que a solução all (k in d ...). Sugeri isso como uma edição, mas ele foi rejeitado por ser melhor adicionar um comentário . Então aqui está me fazendo exatamente isso
miraculixx
@miraculixx Não é melhor adicionar um comentário. É melhor editar informações relevantes em uma resposta e excluir os comentários.
endolith
1
@ endolith Eu concordo, algumas pessoas obviamente não, como você pode ver na edição rejeitada que eu fiz em primeiro lugar. Enfim, essa é uma discussão para a meta, não por aqui.
22816
Alguém pode explicar isso, por favor? Concluí que {} cria um conjunto, mas como o operador menor que ou igual está trabalhando aqui?
Locane 14/10
1
@Locane O operador <= testa se o primeiro conjunto é um subconjunto do segundo conjunto. Você também pode fazer {'foo', 'bar'}. Issubset (somedict). A documentação para a metodologia do conjunto pode ser encontrada aqui: 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
fonte
2
set (d), como usei na minha resposta, é como set (d.keys ()), mas mais rápido, mais curto e eu diria que estilisticamente é preferível.
Alex Martelli
set(d)é o mesmo que set(d.keys())(sem a lista intermediária que d.keys()construtos)
Jochen Ritzel
11

Que tal agora:

if all([key in foo for key in ["foo","bar"]]):
    # do stuff
    pass
Greg
fonte
8
de fato, não apenas desnecessários, positivamente prejudiciais, como impedem o comportamento normal de curto-circuito de all.
287 Alex Martelli
10

Eu acho que este é o mais inteligente e pithonic.

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

Embora eu goste da resposta de Alex Martelli, isso não me parece pitônico. Ou seja, eu pensei que uma parte importante de ser pitônico é ser facilmente compreensível. Com esse objetivo, <=não é fácil de entender.

Embora haja mais caracteres, usar issubset()como sugerido pela resposta de Karl Voigtland é mais compreensível. Como esse método pode usar um dicionário como argumento, uma solução curta e compreensível é:

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

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

Eu gostaria de usar {'foo', 'bar'}no lugar de set(('foo', 'bar')), porque é mais curto. No entanto, não é tão compreensível e acho que os aparelhos são muito facilmente confundidos como um dicionário.

LS
fonte
2
Eu acho que é compreensível quando você entende o que isso significa.
Bobort
Está na documentação como sinônimo .issubset(). Eu acho que estar na documentação do Python o torna Pythonic por padrão.
ingyhere
4

A solução de Alex Martelli set(queries) <= set(my_dict)é o código mais curto, mas pode não ser o mais rápido. Suponha Q = len (consultas) e D = len (my_dict).

Isso leva O (Q) + O (D) para fazer os dois conjuntos e, em seguida (espera-se!), Apenas O (min (Q, D)) para fazer o teste de subconjunto - assumindo, é claro, que o Python configurou a pesquisa é O (1) - este é o pior caso (quando a resposta for Verdadeira).

A solução geradora de hughdbrown (et al?) all(k in my_dict for k in queries)É o pior caso O (Q).

Fatores complicadores:
(1) os loops no gadget baseado em conjunto são todos executados na velocidade C, enquanto o gadget baseado em qualquer loop é feito através do bytecode.
(2) O chamador do gadget baseado em qualquer um pode ser capaz de usar qualquer conhecimento de probabilidade de falha para solicitar os itens de consulta de acordo, enquanto o gadget baseado em conjunto não permite esse controle.

Como sempre, se a velocidade é importante, o benchmarking em condições operacionais é uma boa ideia.

John Machin
fonte
1
O gerador foi mais rápido em todos os casos que tentei. stackoverflow.com/questions/1285911/…
John La Rooy
2

Você pode usar .issubset () , bem

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

Que tal usar lambda?

 if reduce( (lambda x, y: x and foo.has_key(y) ), [ True, "foo", "bar"] ): # do stuff
Jinuk Kim
fonte
2
Esta resposta é a única funcionalmente correta que funcionará no Python 1.5 com uma simples alteração (s / True / 1 /) ... mas não há mais nada a oferecer. E o True thingy seria melhor como o inicializador opcional arg do que amontoado na frente da sequência arg.
John Machin
1

Caso você queira:

  • também obtém os valores para as chaves
  • verifique mais de um dictonary

então:

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
fonte
1

Não é para sugerir que isso não é algo em que você não tenha pensado, mas acho que a coisa mais simples é geralmente a melhor:

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

Jason, () não é necessário em Python.

Juanjo Conti
fonte
3
Ainda assim, eles podem ser de bom estilo ... sem eles, meu cérebro confuso em C ++ sempre se pergunta se será interpretado como "se 'foo in (foo e' bar ') in foo:"
Jeremy Friesner em
1
Eu entendo que eles não são necessários. Eu apenas sinto que eles adicionam clareza neste caso.
Jason Baker
0

Apenas minha opinião sobre isso, existem dois métodos que são fáceis de entender de todas as opções fornecidas. Portanto, meu principal critério é ter um código muito legível, não um código excepcionalmente rápido. Para manter o código compreensível, prefiro as seguintes possibilidades:

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

O fato de "var <= var2.keys ()" executar mais rápido nos meus testes abaixo, prefiro 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
fonte
0

No caso de determinar se apenas algumas chaves correspondem, isso funciona:

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

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

Ainda outra opção para descobrir se apenas algumas chaves correspondem:

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

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

Outra opção para detectar se todas as chaves estão em um ditado:

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
aqui
fonte
-4
>>> ok
{'five': '5', 'two': '2', 'one': '1'}

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

Isso parece funcionar

Prashanth Gowda
fonte
Isso é inteligente e eu estava convencido de que não funcionou até que eu tentei. Suspeitei que o ()primeiro seria avaliado e resultaria True, o qual verificaria se True in ok. Como isso realmente funciona ?!
precisa saber é o seguinte
7
('dois' e 'um' e 'cinco') retorna 'cinco', portanto, na verdade, verifica apenas se 'cinco' está no
ditado