Comment puis-je vérifier que plusieurs clés sont dans un dict en une seule passe?

218

Je veux faire quelque chose comme:

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

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

Comment vérifier si 'foo' et 'bar' sont bien dans dict foo?

Jean-François Corbett
la source

Réponses:

363

Eh bien, vous pouvez le faire:

>>> if all (k in foo for k in ("foo","bar")):
...     print "They're there!"
...
They're there!
Hughdbrown
la source
10
+1, j'aime mieux cela que la réponse de Greg car c'est plus concis ET plus rapide (pas de construction de liste temporaire non pertinente, ET pleine exploitation du court-circuit).
Alex Martelli
4
J'aime tout () et tout (). Ils rendent tellement d'algorithmes tellement plus propres.
hughdbrown
J'ai finalement fini par utiliser cette solution. Cela semblait le meilleur pour les ensembles de données plus volumineux. Lors de la vérification de disons 25 ou 30 touches.
4
C'est une bonne solution grâce aux courts-circuits, surtout si le test échoue le plus souvent; sauf si vous pouvez créer le jeu de clés d'intérêt une seule fois et le vérifier plusieurs fois, auquel cas il setest supérieur. Comme d'habitude ... mesurez-le! -)
Alex Martelli
Je l'utilise chaque fois que ça a l'air plus joli que la façon "normale", avec tous les et ou les ou ... c'est aussi bien parce que vous pouvez utiliser soit "tous" soit "tous" ... en plus vous pouvez avoir " k in foo "ou" k not in foo "selon le test que vous essayez d'effectuer
Terence Honles
123
if {"foo", "bar"} <= myDict.keys(): ...

Si vous êtes toujours sur Python 2, vous pouvez le faire

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

Si vous êtes toujours sur très ancien Python <= 2.6, vous pouvez faire appel setau dict, mais il itérera sur tout le dict pour construire l'ensemble, et c'est lent:

if set(("foo", "bar")) <= set(myDict): ...
Alex Martelli
la source
Cela semble bon! La seule chose que je n'aime pas, c'est que vous devez créer des ensembles temporaires, mais c'est très compact. Je dois donc dire ... belle utilisation des ensembles!
Terence Honles
17
En python 3, vous pouvez dire set(("foo","bar")) <= myDict.keys()ce qui évite l'ensemble temporaire, c'est donc beaucoup plus rapide. Pour mes tests, c'est à peu près la même vitesse que l'utilisation de all lorsque la requête était de 10 éléments. Cela devient plus lent à mesure que la requête grossit.
John La Rooy
1
J'ai posté certains de mes tests comme réponse. stackoverflow.com/questions/1285911/…
John La Rooy
30
if {'foo', 'bar'} <= set(myDict): ...
Boris Raicheff
11
Pour tous ceux qui se demandent pourquoi cela fonctionne: l'opérateur <= est le même que la méthode use .set issubset (): docs.python.org/3/library/stdtypes.html#set-types-set-frozenset
edepe
41

Plateforme de benchmarking simple pour 3 des alternatives.

Mettez vos propres valeurs pour D et 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
la source
4
Python 2.7 doit d.viewkeys()faire set(q) <= d.viewkeys().
Martijn Pieters
Python 2.7.5a d.keys()aussi la méthode.
Ivan Kharlamov
3
@IvanKharlamov, mais en Python2, il ne retourne pas un objet compatible avecset(q) <= ...
John La Rooy
1
Mon mauvais, vous êtes absolument sur place: ça revient TypeError: can only compare to a set. Désolé! :))
Ivan Kharlamov
1
Pour Python 2 changer l'ordre: d.viewkeys() >= set(q). Je suis venu ici pour essayer de comprendre pourquoi la commande était importante!
Veedrac
34

Vous n'avez pas à envelopper le côté gauche dans un ensemble. Vous pouvez simplement faire ceci:

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

Cela fonctionne également mieux que la all(k in d...)solution.

claytonk
la source
2
Cela fonctionne également mieux que la solution all (k in d ...). J'ai suggéré cela comme une modification, mais il a été rejeté au motif qu'il valait mieux ajouter un commentaire . Voici donc ce que je fais
miraculixx
@miraculixx Il n'est pas préférable d'ajouter un commentaire. Il est préférable de modifier les informations pertinentes dans une réponse et de supprimer les commentaires.
endolith
1
@endolith Je suis d'accord, certaines personnes ne le font évidemment pas comme vous pouvez le voir dans l'édition rejetée que j'ai faite en premier lieu. Quoi qu'il en soit, c'est une discussion pour la méta pas pour ici.
miraculixx
Quelqu'un peut-il expliquer cela s'il vous plaît? J'ai compris que {} crée un ensemble, mais comment fonctionne l'opérateur inférieur ou égal ici?
Locane
1
@Locane L'opérateur <= teste si le premier ensemble est un sous-ensemble du second ensemble. Vous pouvez également faire {'foo', 'bar'}. Issubset (somedict). La documentation de la méthodologie d'ensemble peut être trouvée ici: docs.python.org/2/library/sets.html
Meow
24

Utilisation d' ensembles :

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

Alternativement:

if set(("foo", "bar")) <= set(foo):
    #do stuff
Karl Voigtland
la source
2
set (d) comme je l'ai utilisé dans ma réponse est tout comme set (d.keys ()) mais plus rapide, plus court, et je dirais stylistiquement préférable.
Alex Martelli
set(d)est le même que set(d.keys())(sans la liste intermédiaire qui d.keys()construit)
Jochen Ritzel
11

Que dis-tu de ça:

if all([key in foo for key in ["foo","bar"]]):
    # do stuff
    pass
Greg
la source
8
en effet, non seulement inutiles, positivement nocifs, car ils entravent le comportement normal de court-circuit de all.
Alex Martelli
10

Je pense que c'est le plus intelligent et le plus pithonique.

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

Bien que j'aime la réponse d'Alex Martelli, elle ne me semble pas pythonique. Autrement dit, je pensais qu'une partie importante d'être Pythonique est d'être facilement compréhensible. Avec cet objectif,<= n'est pas facile à comprendre.

Bien qu'il s'agisse de plus de caractères, l'utilisation issubset()comme suggéré par la réponse de Karl Voigtland est plus compréhensible. Puisque cette méthode peut utiliser un dictionnaire comme argument, une solution courte et compréhensible est:

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

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

Je voudrais utiliser {'foo', 'bar'}à la place de set(('foo', 'bar')), car il est plus court. Cependant, ce n'est pas si compréhensible et je pense que les accolades sont trop facilement confondues comme étant un dictionnaire.

LS
la source
2
Je pense que c'est compréhensible une fois que vous comprenez ce que cela signifie.
Bobort
C'est dans la documentation comme synonyme de .issubset(). Je pense qu'être dans la documentation Python en fait Pythonic par défaut.
ingyhere
4

La solution d'Alex Martelli set(queries) <= set(my_dict) est le code le plus court mais peut-être pas le plus rapide. Supposons Q = len (requêtes) et D = len (my_dict).

Cela prend O (Q) + O (D) pour faire les deux ensembles, puis (on espère!) Seulement O (min (Q, D)) pour faire le test du sous-ensemble - en supposant bien sûr que la recherche de l'ensemble Python est O (1) - c'est le pire des cas (lorsque la réponse est vraie).

La solution de générateur de hughdbrown (et al?) all(k in my_dict for k in queries)Est le pire des cas O (Q).

Facteurs de complication:
(1) les boucles dans le gadget basé sur l'ensemble sont toutes effectuées à la vitesse C tandis que le gadget basé sur n'importe quelle boucle passe par le bytecode.
(2) L'appelant du gadget à base quelconque peut être en mesure d'utiliser n'importe quelle connaissance de la probabilité d'échec pour ordonner les éléments de requête en conséquence alors que le gadget à base d'ensemble ne permet aucun contrôle de ce type.

Comme toujours, si la vitesse est importante, l'analyse comparative dans des conditions opérationnelles est une bonne idée.

John Machin
la source
1
Le générateur était plus rapide pour tous les cas que j'ai essayés. stackoverflow.com/questions/1285911/…
John La Rooy
2

Vous pouvez utiliser .issubset () et

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

Que diriez-vous d'utiliser lambda?

 if reduce( (lambda x, y: x and foo.has_key(y) ), [ True, "foo", "bar"] ): # do stuff
Jinuk Kim
la source
2
Cette réponse est la seule fonctionnellement correcte qui fonctionnera sur Python 1.5 avec un simple changement (s / True / 1 /) ... mais elle n'a rien d'autre à faire. ET le Vrai truc serait mieux comme argument d'initialisation optionnel plutôt que bourré à l'avant de l'argument de séquence.
John Machin
1

Si vous souhaitez:

  • obtenir également les valeurs des clés
  • vérifier plus d'un dicton

puis:

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
la source
1

Pour ne pas suggérer que ce n'est pas quelque chose auquel vous n'avez pas pensé, mais je trouve que la chose la plus simple est généralement la meilleure:

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

Jason, () n'est pas nécessaire en Python.

Juanjo Conti
la source
3
Pourtant, ils pourraient être de bon style ... sans eux, mon cerveau C ++ - étonné se demande toujours si ça va être interprété comme "si 'foo in (foo et' bar ') in foo:"
Jeremy Friesner
1
Je comprends qu'ils ne sont pas nécessaires. Je pense juste qu'ils ajoutent de la clarté dans ce cas.
Jason Baker
0

Juste mon avis à ce sujet, il existe deux méthodes faciles à comprendre pour toutes les options données. Donc, mon critère principal est d'avoir un code très lisible, pas un code exceptionnellement rapide. Pour garder le code compréhensible, je préfère les possibilités données:

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

Le fait que "var <= var2.keys ()" s'exécute plus rapidement dans mes tests ci-dessous, je préfère celui-ci.

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
la source
0

Dans le cas de déterminer si seules certaines clés correspondent, cela fonctionne:

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

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

Encore une autre option pour trouver si seules certaines clés correspondent:

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

if any_keys_i_seek & my_dict.keys():
    # code_here
    pass
ici
la source
0

Une autre option pour détecter si toutes les clés sont dans 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
ici
la source
-4
>>> ok
{'five': '5', 'two': '2', 'one': '1'}

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

Cela semble fonctionner

Prashanth Gowda
la source
C'est intelligent et j'étais convaincu que cela ne fonctionnait pas jusqu'à ce que je l'essaie moi-même. Je soupçonnais que le premier ()serait évalué et qu'il en résulterait True, ce qui vérifierait ensuite si True in ok. Comment cela fonctionne-t-il réellement?!
durden2.0
7
(«deux» et «un» et «cinq») renvoie «cinq», il ne vérifie donc que si «cinq» est sur le dict
HardQuestions