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