Должен ли я использовать has_key () или in в Python dicts?

913

Интересно, что лучше сделать:

d = {'a': 1, 'b': 2}
'a' in d
True

или:

d = {'a': 1, 'b': 2}
d.has_key('a')
True
igorgue
источник

Ответы:

1288

in определенно более питонический.

На самом деле has_key()был удален в Python 3.x .

тонфа
источник
3
В дополнение, в Python 3, чтобы проверить наличие в значениях, вместо клавиш, попробуйте >>> 1 в d.values ()
риза
217
Тем не менее, нужно избегать одной ошибки: «key in some_dict» вместо «key in some_dict.keys ()». Оба семантически эквивалентны, но с точки зрения производительности последний намного медленнее (O (n) против O (1)). Я видел, как люди делают "в dict.keys ()", думая, что это более явно и, следовательно, лучше.
Адам Паркин
2
@AdamParkin Я продемонстрировал ваш комментарий в своем ответе stackoverflow.com/a/41390975/117471
Бруно Броноски
8
@AdamParkin В Python 3 keys()это просто набор-подобный вид словаря, а не его копия, x in d.keys()как и O (1). Тем не менее, x in dболее Pythonic.
Артур Такка
2
@ AdamParkin Интересно, я этого не видел. Я предполагаю, что это потому, что x in d.keys()должен создать и уничтожить временный объект, в комплекте с распределением памяти, которое влечет за собой, где x in d.keys()просто делает арифметическую операцию (вычисление хеша) и делает поиск. Обратите внимание, что d.keys()это примерно в 10 раз больше, чем на самом деле. Я не проверял, но я все еще уверен, что это только O (1).
Артур Такка
253

in выигрывает, не только в элегантности (и не устаревшей ;-), но и в производительности, например:

$ python -mtimeit -s'd=dict.fromkeys(range(99))' '12 in d'
10000000 loops, best of 3: 0.0983 usec per loop
$ python -mtimeit -s'd=dict.fromkeys(range(99))' 'd.has_key(12)'
1000000 loops, best of 3: 0.21 usec per loop

Хотя следующее наблюдение не всегда верно, вы заметите, что обычно в Python более быстрое решение более элегантно и Pythonic; вот почему -mtimeitТАК полезно - это не просто экономия сотен наносекунд здесь и там! -)

Алекс Мартелли
источник
4
Спасибо за это, сделав проверку, что «в some_dict» на самом деле намного проще O (1) (попробуйте увеличить 99, чтобы сказать 1999, и вы увидите, что время выполнения примерно одинаково).
Адам Паркин
2
has_keyПохоже, O (1) тоже.
Дан-г-н
42

Используйте, dict.has_key()если (и только если), ваш код должен быть запущен версиями Python ранее 2.3 (когда он key in dictбыл представлен).

Джон Мачин
источник
1
Обновление WebSphere в 2013 году использует Jython 2.1 в качестве основного языка сценариев. Так что, к сожалению, это еще полезно отметить через пять лет после того, как вы это заметили.
ArtOfWarfare,
23

Есть один пример, где на inсамом деле убивает вашу производительность.

Если вы используете inна O (1) контейнер , который реализует только __getitem__и , has_key()но не __contains__вы Превратить O (1) поиск в поиск O (N) (как inвозвращается к линейному поиску через __getitem__).

Исправление очевидно тривиально:

def __contains__(self, x):
    return self.has_key(x)
Шленка
источник
6
Этот ответ был применим, когда он был опубликован, но 99,95% читателей могут смело игнорировать его. В большинстве случаев, если вы работаете с чем-то таким неясным, вы это знаете.
wizzwizz4
2
Это действительно не проблема. has_key()является специфическим для Python 2 словарей . in/ __contains__является правильным API для использования; для тех контейнеров, где полное сканирование неизбежно, в любом случае нет никакого has_key()метода , и если есть подход O (1), то это будет зависеть от варианта использования, и поэтому разработчик должен выбрать правильный тип данных для проблемы.
Мартин Питерс
15

has_keyэто словарный метод, но он inбудет работать с любой коллекцией, и даже если __contains__он отсутствует, inбудет использовать любой другой метод для итерации коллекции, чтобы выяснить это.

u0b34a0f6ae
источник
1
И также работает на итераторах "x в xrange (90, 200) <=> 90 <= x <200"
u0b34a0f6ae
1
…: Это выглядит как очень плохая идея: 50 операций вместо 2.
Clément
1
@ Clément В Python 3 довольно эффективно проводить inтесты на rangeобъектах. Однако я не уверен в его эффективности на Python 2 xrange. ;)
PM 2Ring
@ Clément отсутствует в Python 3; __contains__может тривиально вычислить, находится ли значение в диапазоне или нет.
Мартин Питерс
1
@AlexandreHuat Ваше время включает накладные расходы на создание нового rangeэкземпляра каждый раз. При использовании одного ранее существующего экземпляра тест "целое число в диапазоне" выполняется примерно на 40% быстрее.
Мистер Мияги
14

Решение dict.has_key () устарело, используйте 'in' - превосходный текстовый редактор 3

Здесь я взял пример словаря под названием «возраст» -

ages = {}

# Add a couple of names to the dictionary
ages['Sue'] = 23

ages['Peter'] = 19

ages['Andrew'] = 78

ages['Karren'] = 45

# use of 'in' in if condition instead of function_name.has_key(key-name).
if 'Sue' in ages:

    print "Sue is in the dictionary. She is", ages['Sue'], "years old"

else:

    print "Sue is not in the dictionary"
Грина Моди
источник
6
Правильно, но ответ уже получен, добро пожаловать в Stackoveflow, спасибо за пример, всегда проверяйте ответы!
igorgue
@igorgue я не уверен насчет ее отрицательных голосов. Ее ответ может быть похож на уже отвеченный, но она приводит пример. Разве этого не достаточно, чтобы быть ответом ТАК?
Акшат Агарвал
14

Расширяя тесты производительности Алекса Мартелли с комментариями Адама Паркина ...

$ python3.5 -mtimeit -s'd=dict.fromkeys(range( 99))' 'd.has_key(12)'
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/timeit.py", line 301, in main
    x = t.timeit(number)
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
    d.has_key(12)
AttributeError: 'dict' object has no attribute 'has_key'

$ python2.7 -mtimeit -s'd=dict.fromkeys(range(  99))' 'd.has_key(12)'
10000000 loops, best of 3: 0.0872 usec per loop

$ python2.7 -mtimeit -s'd=dict.fromkeys(range(1999))' 'd.has_key(12)'
10000000 loops, best of 3: 0.0858 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(  99))' '12 in d'
10000000 loops, best of 3: 0.031 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(1999))' '12 in d'
10000000 loops, best of 3: 0.033 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(  99))' '12 in d.keys()'
10000000 loops, best of 3: 0.115 usec per loop

$ python3.5 -mtimeit -s'd=dict.fromkeys(range(1999))' '12 in d.keys()'
10000000 loops, best of 3: 0.117 usec per loop
Бруно Броноски
источник
Замечательная статистика, иногда неявная, может быть лучше, чем явная (по крайней мере, по эффективности) ...
Varun
Спасибо, @varun. Я забыл об этом ответе. Мне нужно делать такие проверки чаще. Я регулярно читаю длинные темы, где люди спорят о том, как лучше всего делать что-то. Но я редко помню, как легко было получить доказательства .
Бруно Броноски
0

Если у вас есть что-то вроде этого:

t.has_key(ew)

измените его на ниже для работы на Python 3.X и выше:

key = ew
if key not in t
Харшита Джавар
источник
6
Нет, вы перевернули тест. t.has_key(ew)возвращает, Trueесли значение ewссылки также является ключом в словаре. key not in tвозвращает, Trueесли значение отсутствует в словаре. Более того, key = ewпсевдоним очень, очень избыточен. Правильное написаниеif ew in t . Вот что уже сказал вам принятый ответ за 8 лет до этого.
Мартин Питерс