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.