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

Должен ли я тестировать 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'

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

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

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


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

try:
    import foo
except ImportError:
    import baz

Ответ 1

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

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

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


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

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

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

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

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(str)

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

try:
    return int(str)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None

Ответ 2

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

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

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

Ответ 3

Использование 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" не может быть создан.

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

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

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

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

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

Ответ 6

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

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

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

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

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

Ответ 7

Всякий раз, когда вы используете 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 или вообще никакой ошибки.

Ответ 8

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

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

get (key [, default])

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