Попытка Python-исключение повторного вызова блока

#python #pep

#python #pep

Вопрос:

Плохая практика — не фиксировать исключения внутренней функции и вместо этого делать это при вызове внешней функции? Давайте рассмотрим два примера:

Вариант а)

 def foo(a, b):
    return a / b

def bar(a):
    return foo(a, 0)

try:
    bar(6)
except ZeroDivisionError:
    print("Error!")
  

Pro: очиститель (на мой взгляд)
Против: вы не можете определить, какие исключения bar возникают, не глядя на foo

Вариант b)

 def foo(a, b):
    return a / b

def bar(a):
    try:
        ret = foo(a, 0)
    except ZeroDivisionError:
        raise

    return ret

try:
    bar(6)
except ZeroDivisionError:
    print("Error!")
  

Pro: явный
Против: вы просто пишете блок try-except, который повторно вызывает исключение. Тоже некрасиво, на мой взгляд

Другие варианты?

Я понимаю, что если вы хотите что-то сделать с исключением или сгруппировать несколько исключений вместе, вариант b является единственным выбором. Но что, если вы хотите повторно вызвать только некоторые конкретные исключения как есть?

Я не смог найти в PEP ничего, что проливало бы некоторый свет на это.

Комментарии:

1. Почему в варианте b вы хотите повторно поднять? Просто вызвав bar, он будет поднят в любом случае

2. @Karl это именно мой вопрос, можно ли написать вариант а. Проблема в том, что вы не сразу знаете, какие исключения может вызвать панель исключений, просматривая код. Таким образом, возможный аргумент для повторного вызова должен быть явным.

3. Я бы выбрал вариант а. Если вы хотите знать, откуда возникло исключение, вы всегда можете посмотреть трассировку стека

4. На мой взгляд, лучше всего, если вы обрабатываете исключение непосредственно в функции, где оно может возникнуть (т. Е. Внутри «bar»). Т. е. как в варианте b), но без повторного вызова причины, по которой вы вызываете «bar (6)». Проблема с вариантом b) заключается в том, что вам придется создавать обработку выполнения каждый раз, когда вы вызываете функцию. Лучше, если он сам обрабатывает свои исключения.

5. извините, последнее предложение должно гласить «проблема с вариантом a)»

Ответ №1:

Работа с ошибками

Это плохая практика? На мой взгляд: нет, это не так. В целом это ХОРОШАЯ практика:

 def foo(a, b):
    return a / b

def bar(a):
    return foo(a, 0)

try:
    bar(6)
except ZeroDivisionError:
    print("Error!")
  

Причина проста: код, исправляющий ошибку, сосредоточен в одной точке вашей основной программы.

В некоторых языках программирования исключения, которые потенциально могут быть вызваны, должны быть объявлены на уровне функции / метода. Python отличается: это язык сценариев, в котором отсутствуют подобные функции. Конечно, поэтому иногда вы можете получить исключение совершенно неожиданно, поскольку вы можете не знать, что другой код, который вы вызываете, может вызвать такое исключение. Но это не имеет большого значения: для разрешения этой ситуации у вас есть try...except... в вашей основной программе.

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

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

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

Поэтому вместо …

 def bar(a):
    try:
        ret = foo(a, 0)
    except ZeroDivisionError:
        raise

    return ret
  

… написать:

 def bar(a):
    """
    Might raise ZeroDivisionError
    """
    return foo(a, 0)
  

Или как я бы это написал:

 #
# @throws   ZeroDivisionError       Does not work with zeros.
#
def bar(a):
    return foo(a, 0)
  

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

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

Ошибки вывода

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

 try:
    bar(6)
except ZeroDivisionError:
    print("Error!")
  

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

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

Ответ №2:

Если вы используете книгу «Чистый код» дяди Боба, вы всегда должны разделять логику и обработку ошибок. Это сделало бы вариант a.) предпочтительным решением.

Мне лично нравится называть функции так:

 def _foo(a, b):
    return a / b

def try_foo(a, b):
    try:
        return _foo(a, b)
    except ZeroDivisionError:
        print('Error')


if __name__ == '__main__':
    try_foo(5, 0)