Исключение повторного повышения блока Python try-except

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

Вариант а)

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

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

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

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

Вариант б)

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!")

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

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

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

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


person skd    schedule 02.09.2020    source источник
comment
Почему в варианте б вы хотите сделать ре-рейз? Просто позвонив в бар, он в любом случае поднимется.   -  person Karl    schedule 02.09.2020
comment
@Карл, это именно мой вопрос, можно ли написать вариант а. Проблема в том, что вы не сразу знаете, какая панель исключений может возникнуть, глядя на код. Таким образом, возможный аргумент в пользу ре-рейза должен быть явным.   -  person skd    schedule 02.09.2020
comment
Я бы пошел с вариантом а. Если вы хотите знать, откуда возникло исключение, вы всегда можете посмотреть трассировку стека.   -  person Tomer    schedule 02.09.2020
comment
На мой взгляд, лучше всего, если вы обрабатываете исключение непосредственно в той функции, где оно может произойти (т.е. внутри панели). т.е. как в варианте б), но без повторного рейза, почему вы коллируете bar(6). Проблема с вариантом b) заключается в том, что вам придется создавать обработку исключений каждый раз, когда вы вызываете функцию. Лучше, если он сам обрабатывает свои исключения.   -  person Karl    schedule 02.09.2020
comment
извините, в последнем предложении следует читать проблему с вариантом а)   -  person Karl    schedule 02.09.2020
comment
@Карл, я с тобой согласен. Единственная проблема заключается в том, что бывают случаи, когда функция не может напрямую обрабатывать все свои исключения, и в итоге вы получаете такой код, поэтому мне интересно услышать, что думают люди. Кажется, вариант а) пока выигрывает. Также я ищу любые указатели на официальные руководства по стилю Python.   -  person skd    schedule 02.09.2020
comment
На мой взгляд, если вы столкнулись с необработанными исключениями, вам следует вернуться к своей функции и обработать их там. Если у вас нет контроля над функцией (т.е. с использованием внешнего пакета), вам следует дополнительно обернуть их   -  person Karl    schedule 02.09.2020


Ответы (2)


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

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

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

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

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

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

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

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

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

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

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

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 не обеспечивает удобочитаемую трассировку стека в выводе ошибок. Чтобы компенсировать это, я реализовал свою собственную обработку вывода ошибок (повторно используемую в качестве модуля), которая даже использует цвета, но это другое дело и может также немного выходить за рамки этого вопроса.

person Regis May    schedule 02.09.2020

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

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

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)        
person tilman151    schedule 02.09.2020