Почему "за исключением: проходят" плохую практику программирования?

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

try:
    something
except:
    pass

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

Ответ 1

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

Мое объяснение "немного" дольше, поэтому tl; dr он разбивается на это:

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

Но давайте подробно рассмотрим:

Не выполняйте никаких ошибок

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

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

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

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

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

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

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

Старайтесь не пропускать, кроме блоков

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

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

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

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

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

except: pass

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


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

Ответ 2

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

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

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

Ответ 3

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

try:
    something
except:
    pass

как если бы он был вполне допустимым фрагментом кода, вместо того, чтобы бросать NameError. Надеюсь, это не то, что вам нужно.

Ответ 4

Почему "за исключением: передать" плохую практику программирования?

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

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 bad,
        logging.info(e)   # but if it 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. 

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

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

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

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

Ответ 5

>>> import this

Дзен Питона, Тим Петерс

Красивая лучше, чем уродливая.
Явное лучше, чем неявное.
Простой лучше, чем сложный.
Комплекс лучше, чем сложный.
Квартира лучше, чем вложенная.
Рельеф лучше плотного.
Показатели удобочитаемости.
Особые случаи не являются достаточно сложными, чтобы нарушать правила.
Хотя практичность превосходит чистоту.
Ошибки никогда не должны проходить молча.
Если явно не отключено.
Перед лицом двусмысленности откажитесь от соблазна угадать.
Должен быть один - и желательно только один - простой способ сделать это.
Хотя этот путь не может быть очевидным, если вы не голландский.
Теперь лучше, чем никогда.
Хотя никогда не бывает лучше, чем сейчас.
Если внедрение трудно объяснить, это плохая идея.
Если внедрение легко объяснить, это может быть хорошей идеей.
Пространства имен - одна хорошая идея - позвольте сделать еще больше!

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

Ответ 6

Вы должны использовать не менее except Exception:, чтобы избежать блокировки системных исключений, таких как SystemExit или KeyboardInterrupt. Здесь ссылка в документах.

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

Ответ 7

Во-первых, это нарушает два принципа Zen of Python:

  • Явный лучше, чем неявный
  • Ошибки никогда не должны проходить молча

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

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

try:
    something
except:
    logger.exception('Something happened')

Но, как правило, , если вы пытаетесь поймать какое-либо исключение, вы, вероятно, делаете что-то неправильно!

Ответ 8

Конструкция except:pass существенно замалчивает любые исключительные условия, возникающие при выполнении кода, охватываемого блоком try:.

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

Что особенно важно для Python, так это то, что по идиомам этого языка исключения не обязательно являются ошибками. Они часто используются таким образом, конечно, так же, как и на большинстве языков. Но Python, в частности, иногда использовал их для реализации альтернативного пути выхода из некоторых задач кода, которые на самом деле не являются частью обычного исполняемого файла, но, как известно, время от времени возникают, и их можно даже ожидать в большинстве случаев. SystemExit уже упоминался как старый пример, но наиболее распространенным примером в настоящее время может быть StopIteration. Использование исключений таким образом вызвало много споров, особенно когда итераторы и генераторы были впервые представлены Python, но в конечном итоге идея преобладала.

Ответ 9

Причина №1 уже заявлена ​​- она ​​скрывает ошибки, которых вы не ожидали.

(# 2) - Это делает ваш код трудным для других, чтобы читать и понимать. Если вы поймаете FileNotFoundException, когда пытаетесь прочитать файл, то для другого разработчика очевидно, что функциональность, которую должен иметь блок catch. Если вы не укажете исключение, вам нужно дополнительное комментирование, чтобы объяснить, что должен делать блок.

(# 3) - Он демонстрирует ленивое программирование. Если вы используете общий try/catch, это указывает либо на то, что вы не понимаете возможные ошибки во время выполнения вашей программы, либо что вы не знаю, какие исключения возможны в Python. Захват определенной ошибки показывает, что вы понимаете как свою программу, так и диапазон ошибок, которые бросает Python. Это, скорее всего, заставит других разработчиков и кодовых обозревателей доверять вашей работе.

Ответ 10

Итак, какой результат производит этот код?

fruits = [ 'apple', 'pear', 'carrot', 'banana' ]

found = False
try:
     for i in range(len(fruit)):
         if fruits[i] == 'apple':
             found = true
except:
     pass

if found:
    print "Found an apple"
else:
    print "No apples in list"

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

Ответ 11

В общем, вы можете классифицировать любую ошибку/исключение в одной из трех категорий:

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

  • Boneheaded: ваша собственная ошибка, скорее всего, из-за ошибки контроля, ошибок или программирования. Вы должны исправить ошибку. Опять же, вы, безусловно, не должны игнорировать и продолжать.

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

Во всех случаях except: pass оставит вашу программу в неизвестном состоянии, что может нанести больший урон.

Ответ 12

По-моему, у ошибок есть причина появляться, что мой звук глупый, но так оно и есть. Хорошее программирование только вызывает ошибки, когда вам приходится их обрабатывать. Кроме того, как я читал некоторое время назад, "pass-Statement - это оператор, который показывает код, который будет вставлен позже", поэтому, если вы хотите иметь пустое исключение, вы можете сделать это, но для хорошей программы быть частью отсутствует. потому что вы не справляетесь с тем, что должны иметь. Исключительные исключения дают вам возможность исправить входные данные или изменить структуру данных, чтобы эти исключения не повторялись снова (но в большинстве случаев исключения (исключения из сети, общие входные исключения) указывают, что следующие части программы не будут выполняться хорошо. Например, NetworkException может указывать на неработающее сетевое соединение, а программа не может отправлять/получать данные на следующих этапах программы.

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

try:
    #code here
except Error1:
    #exception handle1

except Error2:
    #exception handle2
#and so on

можно переписать следующим образом:

try:
    #code here
except BaseException as e:
    if isinstance(e, Error1):
        #exception handle1

    elif isinstance(e, Error2):
        #exception handle2

    ...

    else:
        raise

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

Ответ 13

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

Проблемы должны быть решены. Так же, как в жизни, в программировании, если вы просто оставляете проблемы в одиночку и пытаетесь их игнорировать, они не просто уходят сами по себе много раз; вместо этого они становятся больше и размножаются. Чтобы не допустить, чтобы проблема возрастала на вас и снова ударялась по дороге, вы либо 1) устраните ее, либо очистите беспорядок после этого, либо 2) сохраните ее и очистите после этого беспорядок.

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

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

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

Ответ 14

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

При всем сказанном, не принимайте какую-либо практику программирования как первостепенное значение. Это глупо. Всегда есть время и место для выполнения блока ignore-all-exceptions.

Другим примером идиотского первостепенного является использование оператора goto. Когда я учился в школе, наш преподаватель научил нас оператору goto просто упомянуть, что ты не должен его использовать, КОГДА-ЛИБО. Не верьте, что люди говорят вам, что xyz никогда не должен использоваться и не может быть сценария, когда это полезно. Всегда есть.

Ответ 15

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

Ответ 16

Поскольку он еще не упоминался, лучше использовать contextlib.suppress:

with suppress(FileNotFoundError):
    os.remove('somefile.tmp')