Хорошая практика использования try-except-else в Python?

Время от времени в Python я вижу блок:

try:
   try_this(whatever)
except SomeException as exception:
   #Handle exception
else:
   return something

В чем причина существования try-except-else?

Мне не нравится такое программирование, поскольку он использует исключения для управления потоком. Однако, если он включен в язык, для этого должна быть веская причина, не так ли?

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

Обычно я обрабатываю исключения как:

something = some_default_value
try:
    something = try_this(whatever)
except SomeException as exception:
    #Handle exception
finally:
    return something

Или, если я действительно ничего не хочу возвращать, если произойдет исключение, тогда:

try:
    something = try_this(whatever)
    return something
except SomeException as exception:
    #Handle exception

Ответ 1

  "Я не знаю, если это из-за невежества, но мне это не нравится вид программирования, так как он использует исключения для управления потоком. "

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

Даже разработчики ядра Python используют исключения для управления потоком, и этот стиль интенсивно внедряется в язык (то есть протокол итератора использует StopIteration, чтобы сигнализировать об окончании цикла).

Кроме того, стиль try-исключением используется для предотвращения условий гонки, присущих некоторым конструкциям "look-before-you-leap". Например, тестирование os.path.exists приводит к получению информации, которая может устареть к тому времени, когда вы ее используете. Аналогично, Queue.full возвращает информацию, которая может быть устаревшей. В этом случае стиль try-исключением-else создаст более надежный код.

"Как я понимаю, исключения не являются ошибками, они должны только быть использованы для исключительных условий "

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

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

Другими словами, ваше понимание того, что "исключения являются исключительными" - это правило, которое имеет смысл в некоторых других языках, но не в Python.

"Однако, если он включен в сам язык, должно быть хорошая причина для этого, не так ли? "

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

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

Здесь есть хороший пост в блоге об обязательности исключений.

Также посмотрите ответ Qaru: Действительно ли исключения являются исключительными ошибками?

"В чем причина существования" попробуй, кроме как "?"

Само условие else интересно. Он запускается, когда нет исключений, но до предложения finally. Это его основная цель.

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

Вариант использования дополнительного незащищенного кода перед финализацией возникает не очень часто. Так что не ожидайте увидеть много примеров в опубликованном коде. Это несколько редко.

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

recip = float('Inf')
try:
    recip = 1 / f(x)
except ZeroDivisionError:
    logging.info('Infinite result')
else:
    logging.info('Finite result')

Другой пример встречается у бегунов на юнит-тестах:

try:
    tests_run += 1
    run_testcase(case)
except Exception:
    tests_failed += 1
    logging.exception('Failing test case: %r', case)
    print('F', end='')
else:
    logging.info('Successful test case: %r', case)
    print('.', end='')

Наконец, наиболее распространенное использование выражения else в блоке try предназначено для небольшого улучшения (выравнивание исключительных результатов и неисключительных результатов при одинаковом уровне отступа). Это использование всегда необязательно и не является строго обязательным.

Ответ 2

  В чем причина существования try-кроме-else?

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

Предложение else будет выполнено, если ошибок не было, и, не выполняя этот код в блоке try, вы избежите неожиданной ошибки. Опять же, обнаружение непредвиденной ошибки может скрыть ошибки.

Пример

Например:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    return something

Пакет "try, кроме" содержит два необязательных предложения: else и finally. Так что на самом деле try-except-else-finally.

else будет оценивать, только если нет исключения из блока try. Это позволяет нам упростить более сложный код ниже:

no_error = None
try:
    try_this(whatever)
    no_error = True
except SomeException as the_exception:
    handle(the_exception)
if no_error:
    return something

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

finally

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

Разбит с помощью псевдокода

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

Например:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle_SomeException(the_exception)
    # Handle a instance of SomeException or a subclass of it.
except Exception as the_exception:
    generic_handle(the_exception)
    # Handle any other exception that inherits from Exception
    # - does not include GeneratorExit, KeyboardInterrupt, SystemExit
    # Avoid bare 'except:'
else: # there was no exception whatsoever
    return something()
    # if no exception, the "something()" gets evaluated,
    # but the return will not be executed due to the return in the
    # finally block below.
finally:
    # this block will execute no matter what, even if no exception,
    # after "something" is eval'd but before that value is returned
    # but even if there is an exception.
    # a return here will hijack the return functionality. e.g.:
    return True # hijacks the return in the else clause above

Это правда, что мы могли бы вместо этого включить код в блок else в блок try, где он будет работать, если не было исключений, но что, если сам этот код вызывает исключение того типа, который мы ' повторно ловить? Если оставить его в блоке try, эта ошибка будет скрыта.

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

Насколько я понимаю, исключения не являются ошибками

В Python большинство исключений - ошибки.

Мы можем просмотреть иерархию исключений с помощью pydoc. Например, в Python 2:

$ python -m pydoc exceptions

или Python 3:

$ python -m pydoc builtins

Даст нам иерархию. Мы видим, что большинство типов Exception являются ошибками, хотя Python использует некоторые из них для таких вещей, как завершение циклов for (StopIteration). Это иерархия Python 3:

BaseException
    Exception
        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
        StopAsyncIteration
        StopIteration
        SyntaxError
            IndentationError
                TabError
        SystemError
        TypeError
        ValueError
            UnicodeError
                UnicodeDecodeError
                UnicodeEncodeError
                UnicodeTranslateError
        Warning
            BytesWarning
            DeprecationWarning
            FutureWarning
            ImportWarning
            PendingDeprecationWarning
            ResourceWarning
            RuntimeWarning
            SyntaxWarning
            UnicodeWarning
            UserWarning
    GeneratorExit
    KeyboardInterrupt
    SystemExit

Комментатор спросил:

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

Нет, вы не вернете исключение, просто восстановите его с помощью raise, чтобы сохранить трассировку стека.

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise

Или в Python 3 вы можете вызвать новое исключение и сохранить обратную трассировку с цепочкой исключений:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise DifferentException from the_exception

Я уточняю мой ответ здесь.

Ответ 3

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

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

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

try:
   raw_value = int(input())
except ValueError:
   value = some_processed_value
else: # no error occured
   value = process_value(raw_value)

Сравните с тем, как он может работать на других языках:

raw_value = input()
if valid_number(raw_value):
    value = process_value(int(raw_value))
else:
    value = some_processed_value

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

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

Ответ 4

Является ли хорошей практикой использование try-except-else в python?

Ответ на этот вопрос заключается в том, что он зависит от контекста. Если вы это сделаете:

d = dict()
try:
    item = d['item']
except KeyError:
    item = 'default'

Это демонстрирует, что вы не очень хорошо знаете Python. Эта функциональность инкапсулирована в метод dict.get:

item = d.get('item', 'default')

Блок try/except - гораздо более визуально загроможденный и многословный способ записи того, что может эффективно выполняться в одной строке с помощью атомного метода. Существуют и другие случаи, когда это верно.

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

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

Ответ 5

Посмотрите следующий пример, который иллюстрирует все о try-кроме-else-finally:

for i in range(3):
    try:
        y = 1 / i
    except ZeroDivisionError:
        print(f"\ti = {i}")
        print("\tError report: ZeroDivisionError")
    else:
        print(f"\ti = {i}")
        print(f"\tNo error report and y equals {y}")
    finally:
        print("Try block is run.")

Реализуйте это и приходите:

    i = 0
    Error report: ZeroDivisionError
Try block is run.
    i = 1
    No error report and y equals 1.0
Try block is run.
    i = 2
    No error report and y equals 0.5
Try block is run.

Ответ 6

Вы должны быть осторожны с использованием блока finally, поскольку это не то же самое, что использовать блок else в try, кроме. Блок finally будет запускаться независимо от результата попытки, кроме.

In [10]: dict_ = {"a": 1}

In [11]: try:
   ....:     dict_["b"]
   ....: except KeyError:
   ....:     pass
   ....: finally:
   ....:     print "something"
   ....:     
something

Как все отметили, используя блок else, ваш код становится более читаемым и работает только тогда, когда исключение не выбрано

In [14]: try:
             dict_["b"]
         except KeyError:
             pass
         else:
             print "something"
   ....:

Ответ 7

Всякий раз, когда вы видите это:

try:
    y = 1 / x
except ZeroDivisionError:
    pass
else:
    return y

Или даже это:

try:
    return 1 / x
except ZeroDivisionError:
    return None

Рассмотрим это вместо:

import contextlib
with contextlib.suppress(ZeroDivisionError):
    return 1 / x

Ответ 8

Это мой простой фрагмент о том, как понять блок try-исключением-else-finally в Python:

def div(a, b):
    try:
        a/b
    except ZeroDivisionError:
        print("Zero Division Error detected")
    else:
        print("No Zero Division Error")
    finally:
        print("Finally the division of %d/%d is done" % (a, b))

Давай попробуем div 1/1:

div(1, 1)
No Zero Division Error
Finally the division of 1/1 is done

Давай попробуем div 1/0

div(1, 0)
Zero Division Error detected
Finally the division of 1/0 is done

Ответ 9

Просто потому, что никто не опубликовал это мнение, я бы сказал, что

избегайте положений else в try/excepts, потому что они незнакомы большинству людей

В отличие от ключевых слов try, except и finally, значение предложения else не самоочевидно; это менее читабельно. Поскольку он используется не очень часто, люди, читающие ваш код, захотят перепроверить документы, чтобы убедиться, что они понимают, что происходит.

(Я пишу этот ответ именно потому, что я нашел try/except/else в своей кодовой базе, и это вызвало момент wtf и заставило меня заняться поиском).

Поэтому везде, где я вижу код, подобный примеру OP:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    # do some more processing in non-exception case
    return something

Я бы предпочел рефакторинг для

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    return  # <1>
# do some more processing in non-exception case  <2>
return something
  • & lt; 1> явное возвращение ясно показывает, что в исключительном случае мы закончили работу

  • & lt; 2> как приятный незначительный побочный эффект, значение coee, которое раньше было в блоке else, уменьшается на один уровень.

Ответ 10

ОП, ВЫ ПРАВИЛЬНО. Остальное после try/except в Python является уродливым. это приводит к другому объекту управления потоком, где нет необходимости:

try:
    x = blah()
except:
    print "failed at blah()"
else:
    print "just succeeded with blah"

Полностью явный эквивалент:

try:
    x = blah()
    print "just succeeded with blah"
except:
    print "failed at blah()"

Это намного яснее, чем предложение else. Остальное после try/except не часто записывается, поэтому требуется некоторое время, чтобы понять, что подразумевается.

Просто потому, что вы МОЖЕТЕ сделать что-то, не означает, что вы ДОЛЖНЫ что-то сделать.

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

Просто мои 5 центов здесь. Я должен зайти позади и очистить много кода, написанного 1-м годом от разработчиков колледжа, которые считают, что они умны и хотят писать код в какой-то uber-tight, uber-efficient, когда это просто заставляет чтобы попытаться прочитать/изменить позже. Я голосую за удобочитаемость каждый день и дважды по воскресеньям.