Как определить, было ли возбуждено исключение, когда вы находитесь в блоке 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)

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


Поскольку некоторые комментарии запрашивают меньше "М" в MCVE, вот еще несколько предположений в прецеденте. Реальная проблема заключается в эскалации уровней ведения журнала.

  • Фанки-код является третьим лицом и не может быть изменен.
  • Исключение сбоя и трассировка стека не содержат полезной диагностической информации, поэтому использование logger.exception в 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)

Ответ 1

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(...)

Ответ 2

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

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

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')

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

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

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

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

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

Использование 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 an *unhandled* exception, e.g. NOT a HandleThis exception
        print('funky code raised')

Ответ 3

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

Ответ 4

Вы можете легко назначить выбранное исключение переменной и использовать ее в блоке 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'

Ответ 5

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

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')