Лучше «попробовать» что-нибудь и поймать исключение или проверить, если возможно сначала избежать исключения?

139

Должен ли я проверить ifчто-то действительно или просто tryсделать это и поймать исключение?

  • Есть ли надежная документация о том, что предпочтителен один из способов?
  • Является ли один способ более питоническим ?

Например, я должен:

if len(my_list) >= 4:
    x = my_list[3]
else:
    x = 'NO_ABC'

Или:

try:
    x = my_list[3]
except IndexError:
    x = 'NO_ABC'

Некоторые мысли ...
20 человек говорит:

Ошибки никогда не должны проходить бесшумно.
Если явно не молчать.

Следует ли использовать tryвместо вместо ifинтерпретировать ошибку, как молчание? И если так, вы явно заставляете его замолчать, используя его таким образом, поэтому делаете это нормально?


Я не имею в виду ситуации, когда вы можете делать вещи только в одном направлении; например:

try:
    import foo
except ImportError:
    import baz
Чаун
источник

Ответы:

161

Вы должны отдавать предпочтение try/exceptболее , if/elseесли что приводит

  • ускорения (например, путем предотвращения дополнительных поисков)
  • более чистый код (меньше строк / легче читать)

Часто они идут рука об руку.


скорость окна

В случае попытки найти элемент в длинном списке:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

попытка, кроме как лучший вариант, когда, indexвероятно, находится в списке, а ошибка IndexError обычно не вызывается. Таким образом, вы избежите необходимости дополнительного поискаif index < len(my_list) .

Python поощряет использование исключений, которые вы рассматриваете как фразу из Dive Into Python . Ваш пример не только обрабатывает исключение (изящно), вместо того, чтобы пропустить его молча , но и исключение возникает только в исключительном случае отсутствия индекса (отсюда и слово исключение !).


более чистый код

В официальной документации по Python упоминается EAFP : проще просить прощения, чем разрешения, и Роб Найт отмечает, что ловит ошибки, а не их избегает , может привести к получению более чистого и удобного для чтения кода. Его пример говорит это так:

Хуже (LBYL «смотри, прежде чем прыгнуть») :

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

Лучше (EAFP: проще просить прощения, чем разрешения) :

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None
Remi
источник
if index in mylistИндекс тестов является элементом mylist, а не возможным индексом. Вы бы хотели if index < len(mylist)вместо этого.
ychaouche
1
Это имеет смысл, но где я могу найти документацию, которая проясняет, какие возможные исключения могут быть сгенерированы для int (). docs.python.org/3/library/functions.html#int Я не могу найти эту информацию здесь.
BrutalSimplicity
Напротив, вы можете добавить это дело (от офф. Док ) к своему ответу, когда вы предпочитаете, if/elseчемtry/catch
rappongy
20

В этом конкретном случае вы должны использовать что-то еще полностью:

x = myDict.get("ABC", "NO_ABC")

В общем, хотя: если вы ожидаете, что тест не пройдёт часто, используйте if. Если тест стоит дорого по сравнению с простой попыткой операции и перехватом исключения, если оно не выполнено, используйте try. Если ни одно из этих условий не применимо, делайте все, что читается легче.

сумеречный
источник
1
+1 за объяснение под примером кода, который находится на месте.
Ktdrv
4
Я думаю, совершенно ясно, что это не то, о чем он просил, и теперь он отредактировал пост, чтобы сделать его еще более понятным.
AGF
9

Использование tryи exceptнепосредственно, а не внутри ifохраны всегда должно быть сделано, если есть какая-либо возможность состояния гонки. Например, если вы хотите убедиться, что каталог существует, не делайте этого:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

Если другой поток или процесс создает каталог между isdirи mkdir, вы выйдете. Вместо этого сделайте это:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

Это завершится, только если каталог 'foo' не может быть создан.

Джон Коуэн
источник
7

Если до того, как вы это сделаете, проверить, не произойдет ли что-нибудь, тривиально, вы, вероятно, должны это одобрить. В конце концов, создание исключений (включая связанные с ними трассировки) занимает много времени.

Исключения следует использовать для:

  1. неожиданные вещи, или ...
  2. вещи, где вам нужно прыгнуть больше, чем на один уровень логики (например, где breakвы не достаточно далеко) или ...
  3. вещи, где вы точно не знаете, что будет обрабатывать исключение раньше времени, или ...
  4. вещи, где проверка заблаговременно до сбоя стоит дорого (по сравнению с простой попыткой операции)

Обратите внимание, что часто реальным ответом является «ни один» - например, в вашем первом примере, что вы действительно должны сделать, это просто использовать .get()для обеспечения значения по умолчанию:

x = myDict.get('ABC', 'NO_ABC')
янтарный
источник
за исключением того, что if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'это на самом деле часто быстрее, чем использовать get, к сожалению. Не сказать, что это самый важный критерий, но это то, что нужно знать.
AGF
@agf: лучше написать четкий и лаконичный код. Если что-то нужно оптимизировать позже, легко вернуться и переписать это, но c2.com/cgi/wiki?PrematureOptimization
Amber
Я знаю это; моя точка зрения, что if / elseи try / exceptможет иметь место даже тогда , когда есть альтернативы по каждому конкретному случаю , поскольку они имеют разные характеристики.
AGF
@agf, знаете ли вы, будет ли улучшен метод get () в будущих версиях, чтобы он был (по крайней мере) так же быстрым, как поиск в явном виде? Кстати, при поиске дважды (как в случае с «ABC» в d: d ['ABC']), выполняется try: d ['ABC'] за исключением KeyError: ... не быстрее?
Реми
2
@Remi Медленная часть .get()- это поиск атрибутов и служебные вызовы функций на уровне Python; использование ключевых слов во встроенных модулях в основном подходит к C. Я не думаю, что в ближайшее время это станет слишком быстрым. Что касается ifvs. try, метод read dict.get () возвращает указатель с некоторой информацией о производительности. Соотношение попаданий и промахов имеет значение ( tryможет быть быстрее, если ключ почти всегда существует), так же как и размер словаря.
AGF
5

Как упоминают другие посты, это зависит от ситуации. Есть несколько опасностей с использованием try /, за исключением того, что вы заранее проверяете достоверность ваших данных, особенно при использовании их в больших проектах.

  • Код в блоке try может иметь шансы нанести ущерб разным видам до того, как будет обнаружено исключение - если вы предварительно проверите заранее с помощью оператора if, вы можете избежать этого.
  • Если код, вызываемый в вашем блоке try, вызывает общий тип исключения, такой как TypeError или ValueError, на самом деле вы можете не поймать то же исключение, которое ожидали отловить - это может быть что-то еще, что вызывает тот же класс исключения до или после того, как вы даже получите линия, где ваше исключение может быть поднято.

например, предположим, что вы имели:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

IndexError ничего не говорит о том, произошло ли это при попытке получить элемент index_list или my_list.

Питер
источник
4

Следует ли использовать try вместо if, если ошибка интерпретируется как молча? И если так, вы явно заставляете его замолчать, используя его таким образом, поэтому делаете это нормально?

Использование try- это подтверждение того, что ошибка может пройти, что противоположно тому, чтобы она прошла без предупреждения. Использование exceptзаставляет его не проходить вообще.

Использование try: except:предпочтительнее в тех случаях, когда if: else:логика более сложна. Простое лучше, чем сложное; комплекс лучше, чем сложный; и проще просить прощения, чем разрешения.

То, что «ошибки никогда не должны проходить молча», является предупреждением, это случай, когда код может вызвать исключение, о котором вы знаете, и где ваш дизайн допускает такую ​​возможность, но вы не спроектировали таким образом, чтобы иметь дело с этим исключением. Явное глушителей ошибку, на мой взгляд, будет делать что - то подобное passвexcept блоке, который должен быть сделан только с пониманием того, что «ничего не делать» на самом деле являются правильной обработка ошибок в конкретной ситуации. (Это один из немногих случаев, когда мне кажется, что комментарий в хорошо написанном коде действительно необходим).

Однако в вашем конкретном примере ни один из них не подходит:

x = myDict.get('ABC', 'NO_ABC')

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

Карл Кнехтель
источник
3

Всякий раз, когда вы используете try/exceptдля управления потоком, спросите себя:

  1. Легко ли увидеть, когда tryблок успешен, а когда нет?
  2. Вам известны все побочные эффекты внутри tryблока?
  3. Вам известны все случаи, когда tryблок выдает исключение?
  4. Если реализация tryблока изменится, будет ли ваш поток управления работать так, как ожидалось?

Если ответом на один или несколько из этих вопросов является «нет», может потребоваться много прощения; скорее всего от твоего будущего я.


Пример. Недавно я увидел код в более крупном проекте, который выглядел так:

try:
    y = foo(x)
except ProgrammingError:
    y = bar(x)

В разговоре с программистом выяснилось, что предполагаемый поток управления:

Если x - целое число, сделайте y = foo (x).

Если x - это список целых чисел, сделайте y = bar (x).

Это сработало, потому что fooсделал запрос к базе данных, и запрос был бы успешным, если бы xбыл целым числом, и бросил бы, ProgrammingErrorесли бы xбыл список.

Использование try/except- плохой выбор здесь:

  1. Название исключения ProgrammingErrorне выдает фактическую проблему (которая xне является целым числом), что затрудняет понимание того, что происходит.
  2. ProgrammingErrorПоднят во время вызова базы данных, которая время отходы. Все было бы по-настоящему ужасно, если бы выяснилось, что fooчто- то записывает в базу данных до того, как произойдет исключение, или изменит состояние какой-либо другой системы.
  3. Неясно, ProgrammingErrorвозникает ли он только тогда, когда xэто список целых чисел. Предположим, например, что в fooзапросе к базе данных есть опечатка . Это также может поднятьProgrammingError . Следствием является то, что bar(x)теперь также называется, когда xявляется целым числом. Это может вызвать загадочные исключения или привести к непредсказуемым результатам.
  4. try/exceptБлок добавляет требование для всех реализаций будущих foo. Всякий раз, когда мы меняемся foo, мы должны теперь думать о том, как он обрабатывает списки, и быть уверенным, что он выдает ошибку, ProgrammingErrorа не, скажем, AttributeErrorошибку или нет вообще.
Элиас Штреле
источник
0

Для общего значения, вы можете рассмотреть чтение идиом и анти-идиом в Python: исключения .

В вашем конкретном случае, как говорили другие, вы должны использовать dict.get():

получить (ключ [, по умолчанию])

Возвращает значение для ключа, если ключ находится в словаре, иначе по умолчанию. Если default не задано, по умолчанию используется None, поэтому этот метод никогда не вызывает KeyError.

etuardu
источник
Я не думаю, что ссылка охватывает эту ситуацию вообще. Его примеры о работе с вещами которые представляют реальные ошибки , а не того, использовать ли обработку исключений для ожидаемых ситуаций.
AGF
Первая ссылка устарела.
Тимофей Бондарев