Як я можу перевірити, чи є кілька клавіш у дікті за один прохід?


218

Я хочу зробити щось на кшталт:

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

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

Як я можу перевірити, чи "foo", так і "bar" знаходяться в dict foo?

Відповіді:


363

Ну, ви могли це зробити:

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

10
+1, мені це подобається краще, ніж відповідь Грега, тому що він більш стислий І швидший (відсутність створення невідповідного тимчасового списку та повна експлуатація короткого замикання).
Алекс Мартеллі

4
Я люблю всіх () і будь-яких (). Вони роблять стільки алгоритмів набагато чистішими.
hughdbrown

Я врешті-решт в кінцевому рахунку використовував це рішення. Це здавалося найкращим для великих наборів даних. Під час перевірки скажіть 25 або 30 клавіш.

4
Це хороше рішення завдяки короткому замиканню, особливо якщо тест виходить з ладу частіше; якщо ви не зможете створити набір цікавих ключів лише один раз і перевірити його багато разів, і в цьому випадку setвищий. Як завжди ... виміряйте! -)
Алекс Мартеллі

Я використовую це завжди, коли це виглядає приємніше, ніж "звичайний" спосіб, з усіма "і" або "або" ... це також приємно, тому що ви можете використовувати або "все", або "будь-яке" ... крім того, ви можете або мати " k in foo "або" k not in foo "залежно від тесту, який ви намагаєтеся виконати
Terence Honles

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

Якщо ви все ще знаходитесь на Python 2, можете зробити це

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

Якщо ви все ще знаходитесь на дійсно старому Python <= 2.6, ви можете зателефонувати setна дікт, але це повторить весь дікт, щоб створити набір, і це повільно:

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

виглядає добре! Єдине, що мені не подобається, це те, що вам потрібно створювати тимчасові набори, але це дуже компактно. Тому я повинен сказати ... приємне використання наборів!
Теренс Гонлес

17
У python 3 можна сказати, set(("foo","bar")) <= myDict.keys()що дозволяє уникнути тимчасового набору, тому це набагато швидше. Для мого тестування це приблизно така ж швидкість, як і використання всіх, коли запит був 10 предметів. Це стає повільніше, оскільки запит стає більшим.
Джон Ла Рой

1
Деякі свої тести я опублікував як відповідь. stackoverflow.com/questions/1285911/…
Джон Ла Руй

30
if {'foo', 'bar'} <= set(myDict): ...
Борис Райхефф

11
Для всіх, хто цікавиться, чому це працює: оператор <= те саме, що використовувати .set issubset () метод: docs.python.org/3/library/stdtypes.html#set-types-set-frozenset
edepe

41

Проста установка для бенчмаркінгу для 3 варіантів.

Введіть власні значення для D і 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 d.viewkeys()повинен зробити set(q) <= d.viewkeys().
Martijn Pieters

Python 2.7.5має також d.keys()метод.
Іван Харламов

3
@IvanKharlamov, але в Python2 він не повертає об'єкт, сумісний зset(q) <= ...
Джон Ла Рой

1
Мій поганий, ти абсолютно на місці: він повертається TypeError: can only compare to a set. Вибачте! :))
Іван Харламов

1
Для Python 2 змінити порядок: d.viewkeys() >= set(q). Я прийшов сюди, намагаючись з’ясувати, чому порядок має значення!
Ведрак

34

Не потрібно загортати ліву сторону в набір. Ви можете просто зробити це:

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

Це також працює краще, ніж all(k in d...)рішення.


2
Це також працює краще, ніж усі (k в d ...) рішення. Я запропонував це як редагування, але його було відхилено з мотивів, щоб краще додати коментар . Тож ось я роблю саме це
miraculixx

@miraculixx Не краще додати коментар. Краще відредагувати відповідну інформацію у відповідь та видалити коментарі.
ендоліт

1
@endolith Я згоден, деякі люди, очевидно, не так, як ви бачите у відхиленій редакції, яку я зробив в першу чергу. У всякому разі, це дискусія для мета не тут.
miraculixx

Може хтось пояснить це, будь ласка? Я зібрав, що {} створює набір, але як тут працює менший за рівний оператор?
Локан

1
@Locane Оператор <= тестує, якщо перший набір є підмножиною другого набору. Ви також можете зробити {'foo', 'bar'}. Issubset (somedict). У документації за встановленою методології можна знайти тут: docs.python.org/2/library/sets.html
Мяу

24

Використання наборів :

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

Як варіант:

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

2
set (d), як я використовував у своїй відповіді, так само, як set (d.keys ()), але швидший, коротший, і я б сказав, стилістично кращий.
Алекс Мартеллі

set(d)те саме, що set(d.keys())(без проміжного списку, який d.keys()будує)
Йохен Рітцель

11

Як щодо цього:

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

8
Дійсно, не тільки непотрібні, позитивно шкідливі, оскільки перешкоджають нормальній поведінці короткого замикання all.
Алекс Мартеллі


9

Хоча мені подобається відповідь Алекса Мартеллі, мені це не здається пітонічним. Тобто, я вважав, що важливою частиною того, щоб бути пітоніком, є легко зрозуміти. З цією метою <=зрозуміти непросто.

Хоча це більше персонажів, використання, issubset()як це запропонувало відповідь Карла Войгтленда, зрозуміліше. Оскільки цей метод може використовувати словник як аргумент, коротке, зрозуміле рішення:

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

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

Мені хотілося б використовувати {'foo', 'bar'}замість цього set(('foo', 'bar')), оскільки він коротший. Однак це не так зрозуміло, і я думаю, дужки занадто легко плутати, як словник.


2
Я думаю, що це зрозуміло, як тільки ти зрозумієш, що це означає.
Боборт

Це в документації як синонім .issubset(). Я думаю, що в документації на Python за замовчуванням це робить Pythonic.
ingyhere

4

Рішення Алекса Мартеллі set(queries) <= set(my_dict)- найкоротший код, але може бути не найшвидшим. Припустимо, Q = len (запити) і D = len (мій_вирок).

Це потребує O (Q) + O (D), щоб зробити два набори, а потім (один сподівається!) Тільки O (хв (Q, D)) зробити тест підмножини - якщо, звичайно, припустимо, що Python встановив пошук є O (1) - це найгірший випадок (коли відповідь - правда).

Генераторний розчин hughdbrown (та ін.?) all(k in my_dict for k in queries)Є найгіршим випадком O (Q).

Ускладнюючі фактори:
(1) петлі в наборі ґаджета робляться зі швидкістю С, тоді як будь-який базований гаджет перебирається через байт-код.
(2) Абонент будь-якого ґаджета, що базується на будь-якій основі, може мати змогу використовувати будь-які знання про ймовірність невдачі впорядкувати елементи запиту відповідно, тоді як ґаджет на основі набору не дозволяє такого контролю.

Як завжди, якщо важлива швидкість, хороша ідея - тестування в експлуатаційних умовах.


1
Генератор був швидшим для всіх випадків, які я намагався. stackoverflow.com/questions/1285911/…
Джон Ла Руй

2

Ви можете використовувати .issubset () , а також

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

1

Як щодо використання лямбда?

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

2
Ця відповідь є єдиною функціонально правильною, яка працюватиме на Python 1.5 з простою зміною (s / True / 1 /) ... але нічого іншого для цього не буде. І Правда буде краще як опціональний ініціалізатор arg, а не забита в передню частину arg послідовності.
Джон Махін

1

У випадку, якщо ви хочете:

  • також отримайте значення для ключів
  • перевірити більше ніж один довідник

тоді:

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

Не припускати, що це не те, про що ви не думали, але я вважаю, що найпростіша річ зазвичай найкраща:

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

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

Jason, () не потрібні в Python.


3
І все-таки вони можуть бути гарним стилем ... без них мій мозок, що додає C ++, завжди дивується, чи буде його тлумачити як "якби" foo in (foo та 'bar ") в foo:"
Джеремі Фріснер

1
Я розумію, що вони не потрібні. Я просто відчуваю, що вони додають ясності в цьому випадку.
Джейсон Бейкер

0

Щойно я сприймаю це, є два способи, які легко зрозуміти з усіх заданих варіантів. Тому мій головний критерій - це дуже читабельний код, не виключно швидкий код. Щоб код був зрозумілим, я віддаю перевагу наданим можливостям:

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

Те, що "var <= var2.keys ()" виконує швидше в моєму тестуванні нижче, я віддаю перевагу цьому.

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

У разі визначення, чи відповідають лише деякі клавіші, це працює:

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

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

Ще один варіант пошуку, якщо відповідають лише деякі клавіші:

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

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

0

Ще один варіант визначення того, чи всі клавіші знаходяться в диктаті:

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

Це, здається, працює


Це розумно, і я був впевнений, що це не спрацювало, поки я сам не спробував це. Я підозрював, що ()спочатку буде оцінено і призведе до цього True, що потім перевірятиме, чи є True in ok. Як це насправді працює ?!
durden2.0

7
("два" і "один" і "п'ять") повертає "п'ять", тому він насправді перевіряє, чи "п'ять" знаходиться на
диктаті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.