Должен ли я использовать 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

Ответы:


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будет использовать любой другой метод для итерации коллекции, чтобы выяснить это.


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 лет до этого.
— Мартин Питерс
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.