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

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

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

использование contextmanager

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

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

и, как уже указывалось, вместо переменной" 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)

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

используя 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

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

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

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

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

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 для обеспечения области и сделал оператор final except обработчиком общих исключений для исключений, которые вы не хотите обрабатывать.

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

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