Как вы правильно догадались, у этого есть две стороны: перехватывать любую ошибку, не указывая тип исключения послеexcept
, и просто передав ее без каких-либо действий.
Мое объяснение «немного» длиннее - так что д-р разбивается на следующее:
- Не лови никаких ошибок . Всегда указывайте, из каких исключений вы готовы восстанавливаться, и только перехватывайте их.
- Старайтесь избегать прохода, кроме блоков . Если явно не желательно, это обычно не хороший знак.
Но давайте углубимся в детали:
Не лови ни одной ошибки
При использовании try
блока вы обычно делаете это, потому что знаете, что есть вероятность возникновения исключения. Таким образом, у вас также уже есть приблизительное представление о том, что может сломаться и какое исключение может быть выдано. В таких случаях вы ловите исключение, потому что вы можете восстановить после него. Это означает, что вы подготовлены к исключению и имеете какой-то альтернативный план, которому вы будете следовать в случае этого исключения.
Например, когда вы просите пользователя ввести число, вы можете преобразовать ввод, используя int()
который может вызвать a ValueError
. Вы можете легко восстановить это, просто попросив пользователя повторить попытку, поэтому перехват ValueError
и повторный запрос пользователя будут подходящим планом. Другой пример - если вы хотите прочитать какую-то конфигурацию из файла, а этот файл не существует. Поскольку это файл конфигурации, у вас может быть некоторая конфигурация по умолчанию в качестве запасного варианта, поэтому файл не является абсолютно необходимым. Так что поймать FileNotFoundError
и просто применить конфигурацию по умолчанию было бы хорошим планом здесь. Теперь в обоих этих случаях у нас есть очень конкретное исключение, которое мы ожидаем, и у нас есть одинаково конкретный план восстановления после него. Таким образом , в каждом конкретном случае, мы явно только except
что определенные исключение.
Однако, если бы нам нужно было все поймать , то - в дополнение к тем исключениям, от которых мы готовы восстанавливаться, - также есть шанс, что мы получим исключения, которые мы не ожидали и от которых мы действительно не можем восстановить; или не должен оправляться от.
Давайте рассмотрим пример файла конфигурации сверху. В случае отсутствия файла мы просто применили нашу конфигурацию по умолчанию и на более позднем этапе могли бы решить автоматически сохранить конфигурацию (поэтому в следующий раз файл будет существовать). Теперь представьте, что мы получаем IsADirectoryError
илиPermissionError
вместо. В таких случаях мы, вероятно, не хотим продолжать; мы все еще можем применить нашу конфигурацию по умолчанию, но позже мы не сможем сохранить файл. И вполне вероятно, что пользователь хотел иметь собственную конфигурацию, поэтому использование значений по умолчанию, скорее всего, нежелательно. Поэтому мы бы хотели немедленно сообщить об этом пользователю и, возможно, прервать выполнение программы. Но это не то, что мы хотим сделать где-то глубоко внутри какой-то небольшой части кода; это что-то важное на уровне приложения, поэтому его нужно обрабатывать наверху, поэтому пусть возникнет исключение.
Другой простой пример также упоминается в документе идиом Python 2 . Здесь в коде существует простая опечатка, которая приводит к ее поломке. Поскольку мы ловим каждое исключение, мы также ловим NameError
s и SyntaxError
s . Обе ошибки случаются со всеми нами во время программирования; и обе эти ошибки мы абсолютно не хотим включать при отправке кода. Но поскольку мы также поймали их, мы даже не узнаем, что они там произошли, и потеряем какую-либо помощь для их правильной отладки.
Но есть и более опасные исключения, к которым мы вряд ли готовы. Например, SystemError - это обычно то, что случается редко и что мы не можем планировать; это означает, что происходит что-то более сложное, что, скорее всего, мешает нам продолжать текущую задачу.
В любом случае, очень маловероятно, что вы подготовлены ко всему в небольшой части кода, поэтому на самом деле вы должны ловить только те исключения, к которым вы подготовлены. Некоторые люди предлагают по крайней мере поймать, Exception
поскольку это не будет включать в себя такие вещи, как SystemExit
и KeyboardInterrupt
которые по замыслу должны прекратить ваше приложение, но я бы сказал, что это все еще слишком неопределенно. Есть только одно место, где я лично принимаю ловлю Exception
или просто любоеисключение, и это в едином глобальном обработчике исключений уровня приложения, который имеет единственную цель - записать любое исключение, к которому мы не были подготовлены. Таким образом, мы все еще можем хранить как можно больше информации о непредвиденных исключениях, которые мы затем можем использовать для расширения нашего кода, чтобы обрабатывать их явно (если мы сможем восстановить их) или - в случае ошибки - для создания тестовых случаев, чтобы убедиться, это больше не повторится. Но, конечно, это работает, только если мы поймаем те исключения, которые мы уже ожидали, поэтому те, которые мы не ожидали, естественно всплывают.
Старайтесь избегать прохода, кроме блоков
Если явно отлавливать небольшую подборку конкретных исключений, существует множество ситуаций, в которых мы будем в порядке, просто ничего не делая. В таких случаях просто иметь except SomeSpecificException: pass
хорошо. Однако в большинстве случаев это не так, поскольку нам, вероятно, понадобится некоторый код, связанный с процессом восстановления (как упомянуто выше). Это может быть, например, то, что повторяет действие снова, или вместо этого установить значение по умолчанию.
Если это не так, например, потому что наш код уже структурирован, чтобы повторяться до тех пор, пока он не преуспеет, тогда достаточно просто передать. Взяв наш пример сверху, мы можем попросить пользователя ввести число. Поскольку мы знаем, что пользователям нравится не делать то, о чем мы их просим, мы могли бы просто поместить это в цикл, чтобы это выглядело так:
def askForNumber ():
while True:
try:
return int(input('Please enter a number: '))
except ValueError:
pass
Поскольку мы продолжаем попытки до тех пор, пока не будет выдано исключение, нам не нужно делать ничего особенного в блоке кроме, так что это нормально. Но, конечно, можно утверждать, что мы по крайней мере хотим показать пользователю какое-то сообщение об ошибке, чтобы сказать ему, почему он должен повторить ввод.
Тем не менее, во многих других случаях простое указание except
является признаком того, что мы не были готовы к исключению, которое мы ловим. Если эти исключения не являются простыми (например, ValueError
или TypeError
), и причина, по которой мы можем пройти, очевидна, старайтесь избегать просто передачи. Если на самом деле нечего делать (и вы абсолютно уверены в этом), подумайте над добавлением комментария, почему это так; в противном случае разверните блок исключения, чтобы фактически включить некоторый код восстановления.
except: pass
Но худшим нарушителем является комбинация обоих. Это означает, что мы охотно улавливаем любую ошибку, хотя мы абсолютно не готовы к ней, и мы также ничего не делаем с этим. Вы, по крайней мере, хотите зарегистрировать ошибку, а также, вероятно, повторно вызвать ее, чтобы по-прежнему завершать работу приложения (маловероятно, что вы сможете продолжить как обычно после MemoryError). Простое прохождение, хотя и не только поддерживает приложение в некоторой степени живым (в зависимости от того, куда вы его ловите, конечно), но и отбрасывает всю информацию, делая невозможным обнаружение ошибки, что особенно верно, если вы не тот, кто ее обнаруживает.
Итак, суть в следующем: поймать только те исключения, которые вы действительно ожидаете и готовы к восстановлению; все остальные, скорее всего, либо ошибки, которые вы должны исправить, либо что-то, к чему вы все равно не готовы. Проходить определенные исключения - это хорошо, если вам действительно не нужно что-то с ними делать. Во всех остальных случаях это просто знак презумпции и ленивости. И вы определенно хотите это исправить.
logging
модуль на уровне DEBUG, чтобы избежать их использования в производственной среде, но оставляйте их в разработке.