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