Python - 'if foo in dict' vs 'try: dict [foo]'


24

Це менше питання про природу набору качок і більше про перебування пітоніка.

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

if myKey in dict:
    do_some_work(dict[myKey])
else:
    pass

І звичайно, підхід "Прощення проти дозволу" Є Олді.

try:
    do_some_work(dict[myKey])
except KeyError:
    pass

Як мандрівник Python хлопець, я відчуваю, що я бачу, що останній віддав перевагу багато, що здається дивним лише тому, що в Python документи, try/exceptsздається, віддають перевагу, коли є фактична помилка, на відміну від, гм ... відсутності успіху?

Якщо випадковий дикт не має ключа в моєму Dict, і відомо, що він не завжди матиме цей ключ, це спроба / за винятком контекстуально оманливого? Це не помилка програмування, це лише факт даних - у цьому диктаті просто не було саме цього ключа.

Це здається особливо важливим, коли ви дивитесь на синтаксис спробу / винятком / else, який здається дуже корисним, коли потрібно переконатися, що спроба не сприймає занадто багато помилок. Ви можете зробити щось на кшталт:

try:
    foo += bar
except TypeError:
    pass
else:
    return some_more_work(foo)

Хіба це не призведе до проковтування всіляких дивних помилок, які, ймовірно, є наслідком поганого коду? Наведений вище код може просто заважати вам бачити, що ви намагаєтеся додати, 2 + {}і ви ніколи не можете зрозуміти, що якась частина вашого коду жахливо помилилася. Я не рекомендую нам перевіряти всі типи, саме тому це Python, а не JavaScript - але знову ж таки в контексті спроби / за винятком цього, здається, що він повинен застати програму робити те, що не повинно робити, натомість можливості його продовжувати.

Я усвідомлюю, що наведений вище приклад є чимось солом’яним аргументом, і насправді навмисно поганий. Але, враховуючи пітонічне віросповідання, better to ask forgiveness than permissionя не можу не відчути, як напрошується питання про те, де лінія в піску насправді знаходиться між правильним застосуванням if / else vs try / за винятком, зокрема, коли ви знаєте, чого чекати від дані, з якими ви працюєте.

Я навіть не кажу про проблеми, пов'язані зі швидкістю, або найкращу практику тут, я просто непомітно розгублений сприйнятою діаграмою Венна випадків, коли схоже, що це може піти в будь-який бік, але люди помиляються на стороні спроби / за винятком бо "хтось десь сказав, що це піфонічне". Я зробив неправильні висновки щодо застосування цього синтаксису?

python 

Я погоджуюся, що тенденція до спроб за винятком ризику приховати помилки. На мою думку, зазвичай слід уникати спроб, за винятком умов, які ви очікуєте побачити (звичайно, деякі винятки стосуються цього правила).
Гордон Бін

Відповіді:


26

Використовуйте метод GET замість того, щоб :

some_dict.get(the_key, default_value)

... де default_valueповертається значення, якщо the_keyйого немає some_dict. Якщо ви пропустите default_value, Noneповертається, якщо ключ відсутній.

Загалом, в Python, люди , як правило, вважають за краще спробувати / за винятком того , щоб перевірити що - то перше - побачити EAFP запис в глосарії . Зауважте, що багато функцій "тесту на членство" використовують винятки поза кадром.


Це не однакова проблема? Якщо ми намагаємося працювати з a dictі робимо роботу на основі знайденого, це здається, що if myDict.get(a_key) is not None: do_work(myDict[a_key])це важче і ще один приклад неявного вигляду, перш ніж стрибати - плюс це IMHO трохи читабельніший. Можливо, я не розумію dict.get(a_key, a_default)в правильному контексті, але здається, що це попередник написання "не-перемикання-заява if / elses" або магічних значень. Мені подобається те, що робить цей метод, хоча я загляну його вглиб.

1
@Stick - ти робиш:, data = myDict.get(a_key, default)або do_workзробиш правильно, коли дасться default(напр. None), Або ти зробиш if data: do_work(data). В обох випадках потрібен лише один пошук. (Це може бути if data is not None: ..., в будь-якому випадку, це все-таки лише один пошук, і тоді ваша власна логіка може перейняти.)
декретно

1
Тож я вирішив, що dict.get () значно врятував мене в моєму поточному проекті. Це зменшило кількість моїх рядків, очистило багато страшного вигляду try/except/elseмотлоху та в цілому покращило читаність мого коду. Я засвоїв цінний урок, який я чув раніше, але встиг нехтувати прийняттям до душі: "Якщо вам здається, що ви пишете занадто багато коду, зупиніться і перегляньте мовні особливості". Спасибі!!

@Stick - рада почути його :) Мені подобається ця порада, я ніколи її не чув.
detly

1
Технічно, хто з них найшвидший?
Олів'є Понс

4

Відсутній ключ - правило чи виняток ?

З прагматичної точки зору, метання / ловлення набагато повільніше, ніж перевірка того, чи є ключ у таблиці. Якщо відсутній ключ є звичайним явищем - ви повинні використовувати умову.

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

Код, окрім пункту, не повинен бути частиною звичайного недоліку (наприклад, якщо ключа немає, додайте його) - він повинен обробляти помилку.


4
"З прагматичної точки зору, метання / ловлення набагато повільніше, ніж перевірка того, чи є ключ у таблиці" - ні, це не так. Не обов’язково, все одно. Востаннє я перевірив, що найпоширеніші реалізації Python роблять try/ catchверсію швидше.
деті

2
Кидок / ловля в python - це не все настільки велика ефективність угоди, наскільки це часто використовується для контролю потоку в python. Крім того, існує можливість в деяких ситуаціях, коли цей диктант може бути змінений між тестом та доступом.
whatsisname

@whatsisname - я подумав додати це до своєї відповіді, але TBH, якщо у вас є такі загрози для гонки, у вас є набагато більші проблеми, що EAFP проти LBYL: P
декларно

1

У дусі бути "пітонічним" і, конкретно, набирати качку - спроба / крім мене виглядає майже неміцною.

Все, що ви дійсно хочете, - це щось із доступом у формі, але це __getitem__може бути відмінено, щоб зробити щось не зовсім так, як ви очікуєте. Наприклад, використовуючи defaultdictстандартну бібліотеку:

In [1]: from collections import defaultdict

In [2]: foo = defaultdict(int)

In [3]: foo['a'] = 1

In [4]: foo['b']  # Someone did access checking with a try/except exactly as you have in the question
Out[4]: 0

In [5]: 'a' in foo
Out[5]: True

In [6]: 'b' in foo  # We never set it, but now it exists!
Out[6]: True

In [7]: 'c' in foo
Out[7]: False

Оскільки повернене значення за замовчуванням - Falsy, перевірка доступу така, яка працює, більшу частину часу буде просто чудовою. Здається, код також простіший, через що мої колеги та я це робили місяцями.

На жаль, через кілька місяців ці додаткові клавіші фактично спричинили проблеми та важко відстежувати помилки, оскільки 0стали дійсним значенням. І іноді ми б використовували, inщоб перевірити, чи існує ключ, змушуючи код робити різні речі, коли він повинен був бути ідентичним.

Причина, по якій я вважаю, виглядає ламаною, це те, що в дусі Python, що набирає качок, все, що вам слід захотіти, - це щось на зразок диктату, і цілком defaultdictвідповідає цій вимозі, як і будь-який об'єкт, який ви могли б створити, який успадковує dict. Ви не можете гарантувати, що абонент не збирається змінювати його реалізацію пізніше внизу лінії, тому вам слід робити це з найменшими побічними ефектами.

Використовуйте першу версію, if myKey in mydict.


Також зауважте, що мова йде саме про приклад у питанні. Віровина пітона "Краще просити прощення, ніж дозволу" багато в чому має на меті забезпечити правильне дію, коли ти не отримуєш того, чого хочеш. Наприклад, перевірка наявності файлу та спробу читання з нього - принаймні 3 речі, про які я можу придумати верхній частині голови, можуть піти не так:

  • Умовою гонки є те, що файл, можливо, було видалено / переміщено / тощо
  • Ви не маєте дозволу на читання у файлі
  • Файл пошкоджено

Перший, на який ви можете спробувати "попросити дозволу", але за характером умов перегонів це не завжди обов'язково спрацює; другий занадто легко, щоб забути перевірити; третього неможливо перевірити, не намагаючись прочитати файл. У будь-якому випадку, що ви робите після провалу майже напевно буде те ж саме, так що в цьому прикладі, що це краще «просити вибачення», використовуючи спробувати / за винятком.

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