Как определить, возникло ли исключение, когда вы находитесь в блоке finally?

Можно ли сказать, было ли исключение, когда вы находитесь в finally статья? Что-то вроде:

try:
    funky code
finally:
    if ???:
        print('the funky code raised')

Я хочу сделать что-то вроде этого более сухим:

try:
    funky code
except HandleThis:
    # handle it
    raised = True
except DontHandleThis:
    raised = True
    raise
else:
    raised = False
finally:
    logger.info('funky code raised %s', raised)

Мне не нравится, что требуется перехватить исключение, которое вы не собираетесь обрабатывать, просто установить флаг.


Так как некоторые комментарии просят меньше "M" в MCVE, вот еще немного предыстории на примере использования. Актуальная проблема заключается в повышении уровней ведения журнала.

  • Напуганный код является сторонним и не может быть изменен.
  • Исключение сбоя и трассировка стека не содержат никакой полезной диагностической информации, поэтому использование logger.exception в блоке исключений здесь не поможет.
  • Если прикольный код возник, то некоторая информация, которую мне нужно увидеть, уже записана на уровне DEBUG. Мы не делаем и не можем обработать ошибку, но хотим увеличить регистрацию DEBUG, потому что там есть необходимая информация.
  • Прикольный код не поднимается, большую часть времени. Я не хочу повышать уровни регистрации для общего случая, потому что это слишком многословно.

Следовательно, код выполняется в контексте захвата журнала (который настраивает настраиваемые обработчики для перехвата записей журнала), и некоторая отладочная информация повторно регистрируется ретроспективно:

try:
    with LogCapture() as log:
        funky_code()  # <-- third party badness
finally:
    mylog = mylogger.WARNING if <there was exception> else mylogger.DEBUG
    for record in log.captured:
        mylog(record.msg, record.args)

5 ответов

Решение
raised = True
try:
    funky code
    raised = False
except HandleThis:
    # handle it
finally:
    logger.info('funky code raised %s', raised)

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

mylog = WARNING
try:
    funky code
    mylog = DEBUG
except HandleThis:
    # handle it
finally:
    mylog(...)

Использование контекстного менеджера

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

class DidWeRaise:
    __slots__ = ('exception_happened', )  # instances will take less memory

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # If no exception happened the `exc_type` is None
        self.exception_happened = exc_type is not None

А затем использовать это внутри try:

try:
    with DidWeRaise() as error_state:
        # funky code
finally:
    if error_state.exception_happened:
        print('the funky code raised')

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

Использование переменной

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

exception_happened = True
try:
    # funky code
except HandleThis:
    # handle this kind of exception
else:
    exception_happened = False
finally:
    if exception_happened:
        print('the funky code raised')

И, как уже указывалось, вместо наличия переменной "toggle" вы можете заменить ее (в данном случае) на желаемую функцию ведения журнала:

mylog = mylogger.WARNING
try:
    with LogCapture() as log:
        funky_code()
except HandleThis:
    # handle this kind of exception
else:
    # In case absolutely no exception was thrown in the try we can log on debug level
    mylog = mylogger.DEBUG
finally:
    for record in log.captured:
        mylog(record.msg, record.args)

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

С помощью sys.exc_info (работает только для необработанных исключений)

Последний подход, о котором я хочу упомянуть, вероятно, бесполезен для вас, но может быть полезен для будущих читателей, которые хотят знать только, есть ли необработанное исключение (исключение, которое не было обнаружено ни в одном except блок или был поднят внутри except блок). В этом случае вы можете использовать sys.exc_info:

import sys

try:
    # funky code
except HandleThis:
    pass
finally:
    if sys.exc_info()[0] is not None:
        # only entered if there's an *unhandled* exception, e.g. NOT a HandleThis exception
        print('funky code raised')

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

@contextmanager
def LogCapture():
    # your existing buffer code here
    level = logging.WARN
    try:
        yield
    except UselessException:
        level = logging.DEBUG
        raise  # Or don't, if you just want it to go away
    finally:
        # emit logs here

Оригинальный ответ

Вы думаете об этом немного в сторону.

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

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

class MyPkgException(Exception):  pass
class MyError(PyPkgException): pass  # If there's another exception type, you can also inherit from that

def do_the_badness():
    try:
        raise FileNotFoundError('Or some other code that raises an error')
    except FileNotFoundError as e:
        raise MyError('File was not found, doh!') from e
    finally:
        do_some_cleanup()

try:
    do_the_badness()
except MyError as e:
    print('The error? Yeah, it happened')

Это решает:

  • Явная обработка исключений, которые вы хотите обработать
  • Обеспечение доступности трассировки стека и оригинальных исключений
  • Разрешение вашему коду, который будет обрабатывать исходное исключение где-то еще, обрабатывать ваше исключение, которое выдается
  • Разрешение некоторого кода обработки исключений верхнего уровня просто перехватить MyPkgException чтобы перехватить все ваши исключения, чтобы он мог что-то регистрировать и выходить с хорошим статусом вместо уродливой трассировки стека

Вы можете легко назначить перехваченное исключение переменной и использовать его в блоке finally, например:

>>> x = 1
>>> error = None
>>> try:
...     x.foo()
... except Exception as e:
...     error = e
... finally:
...     if error is not None:
...             print(error)
...
'int' object has no attribute 'foo'

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

raised = False
try:
    # funky code
except HandleThis:
    # handle it
    raised = True
except Exception as ex:
    # Don't Handle This 
    raise ex
finally:
    if raised:
        logger.info('funky code was raised')

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

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

success = False
try:
    # funky code
    success = True
except HandleThis:
    # handle it
    pass
except Exception as ex:
    # Don't Handle This 
    raise ex
finally:
    if success:
        logger.info('funky code was successful')
    else:
        logger.info('funky code was raised')

Если произошло исключение -> Поместите эту логику в блок (ы) исключений.
Если исключение не произошло -> Поместите эту логику в блок try после точки в коде, где может возникнуть исключение.

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

Другие вопросы по тегам