Чи слід використовувати "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.