Чи слід використовувати "has_key ()" або "in" на диктатах Python?


910

Цікаво, що краще робити:

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

або:

d = {'a': 1, 'b': 2}
d.has_key('a')
True

Відповіді:


1286

in безумовно, більш пітонічний.

Насправді has_key()був видалений в Python 3.x .


3
Як додаток, у Python 3, щоб перевірити наявність значень замість клавіш, спробуйте >>> 1 у d.values ​​()
riza

217
Один напівчарда, якого слід уникати, - це переконатися, що ви робите: "введіть у деякий_вирок", а не "ключ в some_dict.keys ()". Обидва є семантично рівнозначними, але ефективніше останні є значно повільнішими (O (n) проти O (1)). Я бачив, як люди роблять "in dict.keys ()", думаючи, що це явніше і, отже, краще.
Адам Паркін

2
@AdamParkin я показав свій коментар в моїй обороні stackoverflow.com/a/41390975/117471
Bruno Bronosky

8
@AdamParkin У Python 3 - keys()це просто схожий набір у словник, а не копія, x in d.keys()як і O (1). Все-таки x in dє більш пітонічним.
Артур Такка

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 швидше рішення є більш елегантним та пітонічним; ось чому -mtimeitТАК корисно - справа не лише в тому, щоб заощадити сто наносекунд тут і там! -)


4
Дякую за це, переконавшись, що "в some_dict" насправді O (1) набагато простіше (спробуйте збільшити 99, щоб сказати 1999, і ви побачите, що час виконання приблизно такий же).
Адам Паркін

2
has_keyвиявляється також O (1).
dan-gph


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) підхід, то це буде конкретним випадком використання, і тому розробник повинен вибрати правильний тип даних для проблеми.
Martijn Pieters

15

has_keyє словниковим методом, але він inбуде працювати над будь-якою колекцією, і навіть тоді, коли __contains__її немає, inвикористовуватиме будь-який інший метод для повторення колекції, щоб дізнатися це.


1
А також працює над ітераторами "x in xrange (90, 200) <=> 90 <= x <200"
u0b34a0f6ae

1
…: Це виглядає як дуже погана ідея: 50 операцій замість 2.
Клемент

1
@ Clément У Python 3 фактично досить ефективно робити inтести на rangeоб’єктах. Я не дуже впевнений у його ефективності на Python 2 xrange. ;)
PM 2Ring

@ Clément не в Python 3; __contains__можна тривіально обчислити, чи є значення в діапазоні чи ні.
Martijn Pieters

1
@AlexandreHuat Ваш час включає в себе накладні витрати rangeкожного разу створювати новий екземпляр. Використовуючи один, раніше існуючий екземпляр, тест "ціле число в діапазоні" в моїх таймінгах на 40% швидший.
MisterMiyagi

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 Я забув про цю відповідь. Мені потрібно робити подібне тестування частіше. Я регулярно читаю довгі теми, де люди сперечаються про The Best Way ™ робити речі. Але я рідко згадую, як легко було отримати докази .
Бруно Броноський

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 років до цього.
Martijn Pieters
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.