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