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

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

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

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

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

Or:

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

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

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

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


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

try:
    import foo
except ImportError:
    import baz

person chown    schedule 29.09.2011    source источник


Ответы (8)


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

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

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


ускорение

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

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

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

Python поощряет использование исключений, которые вы обрабатываете - это фраза из Погрузитесь в 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
person Remi    schedule 30.09.2011
comment
if index in mylist проверяет, является ли индекс элементом mylist, а не возможным индексом. Вместо этого вам нужно if index < len(mylist). - person ychaouche; 24.06.2013
comment
Это имеет смысл, но где мне найти документацию, которая проясняет, какие возможные исключения могут быть выброшены для int (). docs.python.org/3/library/functions.html#int Я не могу найти здесь эту информацию. - person BrutalSimplicity; 22.06.2019
comment
Напротив, вы можете добавить этот случай (из выкл. doc) к вашему ответу, когда вы должны предпочесть if/else, а не try/catch - person rappongy; 24.07.2020

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

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

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

person Community    schedule 29.09.2011
comment
+1 за объяснение под образцом кода, который подходит. - person ktdrv; 30.09.2011
comment
Я думаю, довольно ясно, что он спрашивал не об этом, и теперь он отредактировал сообщение, чтобы сделать его еще более ясным. - person agf; 30.09.2011

Использование 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'.

person John Cowan    schedule 22.09.2015

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

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

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

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

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

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

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

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

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

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

person Peter    schedule 16.10.2014

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

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

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

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

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

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

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

person Karl Knechtel    schedule 30.09.2011

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

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

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

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

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

person etuardu    schedule 30.09.2011
comment
Я не думаю, что ссылка вообще описывает эту ситуацию. Его примеры касаются обработки вещей, которые представляют реальные ошибки, а не о том, следует ли использовать обработку исключений в ожидаемых ситуациях. - person agf; 30.09.2011
comment
Первая ссылка устарела. - person Timofei Bondarev; 24.08.2016