Вопрос-Ответ

Why is "except: pass" a bad programming practice?

Почему "except: pass" является плохой практикой программирования?

Я часто вижу комментарии по другим вопросам Stack Overflow о том, что использование except: pass не рекомендуется. Почему это плохо? Иногда мне просто все равно, что это за ошибки, и я хочу просто продолжить работу с кодом.

try:
something
except:
pass

Почему использование except: pass блока плохо? Что делает его плохим? Это тот факт, что я pass на ошибке или что я except любую ошибку?

Переведено автоматически
Ответ 1

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

Мое объяснение “немного” длиннее - так что tl; dr оно сводится к следующему:


  1. Не перехватывайте никаких ошибок. Всегда указывайте, из каких исключений вы готовы восстанавливаться, и перехватывайте только те.

  2. Старайтесь избегать передачи блоков except. Обычно это не является хорошим знаком, если это явно не требуется.

Но давайте углубимся в детали:

Не улавливает никакой ошибки

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

Например, когда вы просите пользователя ввести число, вы можете преобразовать входные данные, используя int() что может вызвать ValueError. Вы можете легко восстановить это, просто попросив пользователя попробовать это снова, поэтому подходящим планом было бы перехватить ValueError и снова запросить пользователя. Другим примером может быть ситуация, когда вы хотите прочитать некоторую конфигурацию из файла, а этот файл, оказывается, не существует. Поскольку это конфигурационный файл, у вас может быть какая-то конфигурация по умолчанию в качестве запасного варианта, поэтому файл точно не нужен. Поэтому хорошим планом здесь было бы найти FileNotFoundError и просто применить конфигурацию по умолчанию. Теперь в обоих этих случаях у нас есть очень конкретное ожидаемое исключение и столь же конкретный план восстановления после него. Таким образом, в каждом случае мы явно используем только except это определенное исключение.

Однако, если бы мы собирались перехватывать все, то — в дополнение к тем исключениям, от которых мы готовы восстановиться, — также есть вероятность, что мы получим исключения, которых мы не ожидали, и от которых мы действительно не можем восстановиться; или не должны восстанавливаться.

Давайте возьмем пример файла конфигурации из приведенного выше. В случае отсутствия файла мы просто применили нашу конфигурацию по умолчанию и, возможно, позже решим автоматически сохранить конфигурацию (чтобы в следующий раз файл существовал). Теперь представьте, что вместо этого мы получаем IsADirectoryError или a PermissionError . В таких случаях мы, вероятно, не захотим продолжать; мы все равно могли бы применить нашу конфигурацию по умолчанию, но позже мы не сможем сохранить файл. И вполне вероятно, что пользователь также имел в виду пользовательскую конфигурацию, поэтому использование значений по умолчанию, вероятно, нежелательно. Поэтому мы хотели бы немедленно сообщить пользователю об этом и, вероятно, также прервать выполнение программы. Но это не то, что мы хотим делать где-то глубоко в какой-то небольшой части кода; это что-то важное на уровне приложения, поэтому с этим следует обращаться наверху - так что пусть всплывает исключение.

Другой простой пример также упоминается в документе Python 2 idioms . Здесь в коде присутствует простая опечатка, которая приводит к его прерыванию. Поскольку мы перехватываем каждое исключение, мы также перехватываем NameErrors и SyntaxErrors. Обе ошибки случаются со всеми нами при программировании, и обе ошибки мы категорически не хотим включать при отправке кода. Но поскольку мы также их перехватили, мы даже не узнаем, что они там произошли, и потеряем какую-либо помощь для правильной отладки.

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

В любом случае, маловероятно, что вы готовы ко всему в небольшой части кода, так что на самом деле вам следует перехватывать только те исключения, к которым вы готовы. Некоторые люди предлагают, по крайней мере, catch, Exception поскольку он не будет включать такие вещи, как SystemExit и KeyboardInterrupt которые по замыслу должны завершать работу вашего приложения, но я бы сказал, что это все еще слишком неопределенно. Есть только одно место, где я лично принимаю перехват Exception или просто любого исключения, и это в единственном глобальном обработчике исключений на уровне приложения, который имеет единственное назначение регистрировать любое исключение, к которому мы не были готовы. Таким образом, мы по-прежнему можем сохранять как можно больше информации о неожиданных исключениях, которую затем можем использовать для расширения нашего кода для их явной обработки (если мы сможем восстановиться после них) или — в случае ошибки — для создания тестовых примеров, чтобы убедиться, что это не повторится. Но, конечно, это работает только в том случае, если мы перехватываем только те исключения, которые мы уже ожидали, поэтому те, которых мы не ожидали, естественным образом всплывут.

Старайтесь избегать передачи в блоках except

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

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

def askForNumber ():
while True:
try:
return int(input('Please enter a number: '))
except ValueError:
pass

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

Однако во многих других случаях простая передача в except является признаком того, что мы на самом деле не были готовы к перехватываемому исключению. Если эти исключения не являются простыми (например, ValueError или TypeError), и причина, по которой мы можем передавать, очевидна, старайтесь избегать простого прохождения. Если действительно нечего делать (и вы абсолютно уверены в этом), тогда подумайте о добавлении комментария, почему это так; в противном случае разверните блок except, чтобы фактически включить некоторый код восстановления.

except: pass

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


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

Ответ 2

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

Это уже слишком. В своей голове вы думаете: "Я хочу проигнорировать эту сетевую ошибку". Если что-то неожиданное пойдет не так, то ваш код будет молча продолжен и сломается совершенно непредсказуемым образом, который никто не сможет отладить.

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

Ответ 3

Выполнение вашего псевдокода буквально даже не выдает никакой ошибки:

try:
something
except:
pass

как будто это совершенно корректный фрагмент кода, вместо того, чтобы выбрасывать NameError. Я надеюсь, это не то, что вы хотите.

Ответ 4

Почему "except: pass" - плохая практика программирования?


Почему это плохо?


try:
something
except:
pass

Это улавливает все возможные исключения, включая GeneratorExit, KeyboardInterrupt и SystemExit - исключения, которые вы, вероятно, не собираетесь улавливать. Это то же самое, что и перехват BaseException.

try:
something
except BaseException:
pass

В старых версиях документации говорится:


Поскольку каждая ошибка в Python вызывает исключение, использование except: может привести к тому, что многие ошибки программирования будут выглядеть как проблемы во время выполнения, что затруднит процесс отладки.


Иерархия исключений Python

Если вы перехватываете родительский класс исключений, вы также перехватываете все его дочерние классы. Гораздо элегантнее перехватывать только те исключения, которые вы готовы обработать.

Вот иерархия исключений Python 3 - вы действительно хотите перехватить их все?:

BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
+-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning

Не делайте этого

Если вы используете эту форму обработки исключений:

try:
something
except: # don't just do a bare except!
pass

Тогда вы не сможете прервать свой something блок с помощью Ctrl-C. Ваша программа пропустит все возможные исключения внутри try блока кода.

Вот еще один пример, который будет иметь такое же нежелательное поведение:

except BaseException as e: # don't do this either - same as bare!
logging.info(e)

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

try:
foo = operation_that_includes_int(foo)
except ValueError as e:
if fatal_condition(): # You can raise the exception if it's bad,
logging.info(e) # but if it's fatal every time,
raise # you probably should just not catch it.
else: # Only catch exceptions you are prepared to handle.
foo = 0 # Here we simply assign foo to 0 and continue.

Дальнейшее объяснение на другом примере

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

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

При более точной обработке ошибок ваш код может быть более надежным.

2023-06-20 06:07 python