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

должен ли я проверить 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 интерпретироваться как ошибка, проходящая молча? И если да, то вы явно заставляете его замолчать, используя его таким образом, поэтому делая его ОК?


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

try:
    import foo
except ImportError:
    import baz

8 ответов


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

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

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


ускорение

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

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

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

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


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

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

хуже (LBYL ' посмотрите перед вами leap'):

#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

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

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

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


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

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

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

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

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

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


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

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

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

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

однако в вашем конкретном примере ни то, ни другое не подходит:

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

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


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

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

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

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

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


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

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

get (клавиша[, по умолчанию])

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


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

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

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

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

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

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

и если x-список целые числа, do y = bar (x).

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

используя try/except плохой выбор здесь:

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