Добавление информации в исключение?
Я хочу добиться чего-то вроде этого:
def foo():
try:
raise IOError('Stuff ')
except:
raise
def bar(arg1):
try:
foo()
except Exception as e:
e.message = e.message + 'happens at %s' % arg1
raise
bar('arg1')
Traceback...
IOError('Stuff Happens at arg1')
Но то, что я получаю, это:
Traceback..
IOError('Stuff')
Какие-нибудь подсказки относительно того, как этого достичь? Как это сделать в Python 2 и 3?
Ответы
Ответ 1
Я бы сделал это так, поэтому изменение его типа в foo()
не потребует его изменения и в bar()
.
def foo():
try:
raise IOError('Stuff')
except:
raise
def bar(arg1):
try:
foo()
except Exception as e:
raise type(e)(e.message + ' happens at %s' % arg1)
bar('arg1')
Traceback (most recent call last):
File "test.py", line 13, in <module>
bar('arg1')
File "test.py", line 11, in bar
raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1
Обновление 1
Вот небольшая модификация, которая сохраняет оригинальную трассировку:
...
def bar(arg1):
try:
foo()
except Exception as e:
import sys
raise type(e), type(e)(e.message +
' happens at %s' % arg1), sys.exc_info()[2]
bar('arg1')
Traceback (most recent call last):
File "test.py", line 16, in <module>
bar('arg1')
File "test.py", line 11, in bar
foo()
File "test.py", line 5, in foo
raise IOError('Stuff')
IOError: Stuff happens at arg1
Обновление 2
Для Python 3.x код в моем первом обновлении синтаксически неверен, плюс идея иметь атрибут message
в BaseException
была отозвана при изменении PEP 352 2012-05-16 ( мое первое обновление было опубликовано 2012-03-12). Так что в настоящее время в Python 3.5.2 в любом случае вам нужно что-то делать в этом направлении, чтобы сохранить трассировку, а не жестко кодировать тип исключения в функции bar()
. Также обратите внимание, что там будет строка:
During handling of the above exception, another exception occurred:
в отображаемых сообщениях трассировки.
# for Python 3.x
...
def bar(arg1):
try:
foo()
except Exception as e:
import sys
raise type(e)(str(e) +
' happens at %s' % arg1).with_traceback(sys.exc_info()[2])
bar('arg1')
Обновление 3
Комментатор спросил, есть ли способ, который будет работать как в Python 2, так и в 3. Хотя ответ может показаться "Нет" из-за различий в синтаксисе, есть способ обойти это, используя вспомогательную функцию, такую как reraise()
в дополнительном модуле six
. Поэтому, если вы по какой-либо причине не хотите использовать библиотеку, ниже приведена упрощенная автономная версия.
Также обратите внимание, что, поскольку исключение повторно вызывается в функции reraise()
, оно будет отображаться при любой трассировке, но конечный результат - то, что вы хотите.
import sys
if sys.version_info.major < 3: # Python 2?
# Using exec avoids a SyntaxError in Python 3.
exec("""def reraise(exc_type, exc_value, exc_traceback=None):
raise exc_type, exc_value, exc_traceback""")
else:
def reraise(exc_type, exc_value, exc_traceback=None):
if exc_value is None:
exc_value = exc_type()
if exc_value.__traceback__ is not exc_traceback:
raise exc_value.with_traceback(exc_traceback)
raise exc_value
def foo():
try:
raise IOError('Stuff')
except:
raise
def bar(arg1):
try:
foo()
except Exception as e:
reraise(type(e), type(e)(str(e) +
' happens at %s' % arg1), sys.exc_info()[2])
bar('arg1')
Ответ 2
Если вы пришли сюда в поисках решения для Python 3 , руководство гласит:
При возбуждении нового исключения (вместо того, чтобы использовать пустой raise
для повторного вызова исключения, которое обрабатывается в настоящее время), неявный контекст исключения может быть дополнен явной причиной с помощью from с Повышать:
raise new_exc from original_exc
Пример:
try:
return [permission() for permission in self.permission_classes]
except TypeError as e:
raise TypeError("Make sure your view 'permission_classes' are iterable. "
"If you use '()' to generate a set with a single element "
"make sure that there is a comma behind the one (element,).") from e
Что в итоге выглядит так:
2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
# Traceback removed...
TypeError: Make sure your view Permission_classes are iterable. If
you use parens () to generate a set with a single element make
sure that there is a (comma,) behind the one element.
Превращение совершенно неописуемого TypeError
в красивое сообщение с подсказками к решению без путаницы в оригинальном Исключении.
Ответ 3
Предполагая, что вы не хотите или не можете изменить foo(), вы можете сделать это:
try:
raise IOError('stuff')
except Exception as e:
if len(e.args) >= 1:
e.args = (e.args[0] + ' happens',) + e.args[1:]
raise
Это действительно единственное решение, которое решает проблему в Python 3 без неприятного и запутанного сообщения "Во время обработки вышеуказанного исключения возникло другое исключение".
Если в трассировку стека необходимо добавить линию ре-рейза, то вместо этого будет написано raise e
вместо raise
.
Ответ 4
Один удобный подход, который я использовал, - использовать атрибут класса как хранилище для деталей,
поскольку атрибут класса доступен как из класса, так и класса:
class CustomError(Exception):
details = None
Затем в вашем коде:
exc = CustomError('Some message')
exc.details('Details -- add whatever you want')
raise exc
И при обнаружении ошибки:
except CustomError, e:
# Do whatever you want with the exception instance
print e
print e.details
Ответ 5
В отличие от предыдущих ответов, это работает перед исключениями с очень плохим __str__
.
Однако он модифицирует тип, чтобы исключить бесполезные реализации __str__
.
Мне бы хотелось найти дополнительное усовершенствование, которое не изменяет тип.
from contextlib import contextmanager
@contextmanager
def helpful_info():
try:
yield
except Exception as e:
class CloneException(Exception): pass
CloneException.__name__ = type(e).__name__
CloneException.__module___ = type(e).__module__
helpful_message = '%s\n\nhelpful info!' % e
import sys
raise CloneException, helpful_message, sys.exc_traceback
class BadException(Exception):
def __str__(self):
return 'wat.'
with helpful_info():
raise BadException('fooooo')
Исходный трассировка и тип (имя) сохраняются.
Traceback (most recent call last):
File "re_raise.py", line 20, in <module>
raise BadException('fooooo')
File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
self.gen.throw(type, value, traceback)
File "re_raise.py", line 5, in helpful_info
yield
File "re_raise.py", line 20, in <module>
raise BadException('fooooo')
__main__.BadException: wat.
helpful info!
Ответ 6
Я предоставил фрагмент кода, который я часто использую, когда хочу добавить дополнительную информацию в исключение. Я работаю как в Python 2.7, так и в 3.6.
import sys
import traceback
try:
a = 1
b = 1j
# The line below raises an exception because
# we cannot compare int to complex.
m = max(a, b)
except Exception as ex:
# I create my informational message for debugging:
msg = "a=%r, b=%r" % (a, b)
# Gather the information from the original exception:
exc_type, exc_value, exc_traceback = sys.exc_info()
# Format the original exception for a nice printout:
traceback_string = ''.join(traceback.format_exception(
exc_type, exc_value, exc_traceback))
# Re-raise a new exception of the same class as the original one,
# using my custom message and the original traceback:
raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))
Приведенный выше код приводит к следующему выводу:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
14 raise type(ex)(
15 "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16 (msg, traceback_string))
TypeError: a=1, b=1j
ORIGINAL TRACEBACK:
Traceback (most recent call last):
File "<ipython-input-6-09b74752c60d>", line 7, in <module>
m = max(a, b) # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers
Я знаю, что это немного отклоняется от примера, представленного в вопросе, но, тем не менее, я надеюсь, что кто-то сочтет это полезным.
Ответ 7
Вы можете определить свое собственное исключение, которое наследуется от другого и создать его собственный конструктор для установки значения.
Например:
class MyError(Exception):
def __init__(self, value):
self.value = value
Exception.__init__(self)
def __str__(self):
return repr(self.value)
Ответ 8
Мне пока не нравятся все приведенные ответы. Они все еще слишком многословны. В любом коде и выводе сообщения.
Все, что я хочу иметь, - это трассировка стека, указывающая на исходное исключение, между ними нет никаких исключений, поэтому не нужно создавать новых исключений, просто повторно поднимать оригинал со всеми соответствующими состояниями фрейма стека в нем, что привело туда.
Стив Ховард дал хороший ответ, который я хочу расширить, нет, сократить... только до Python 3.
except Exception as e:
e.args = ("Some failure state", *e.args)
raise
Единственная новая вещь - это расширение/распаковка параметров, что делает его небольшим и достаточно простым для использования.
Попробуйте:
foo = None
try:
try:
state = "bar"
foo.append(state)
except Exception as e:
e.args = ("Appending '"+state+"' failed", *e.args)
raise
print(foo[0]) # would raise too
except Exception as e:
e.args = ("print(foo) failed: " + str(foo), *e.args)
raise
Это даст вам:
Traceback (most recent call last):
File "test.py", line 6, in <module>
foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")
Простая симпатичная печать может быть чем-то вроде
print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))
Ответ 9
Может
except Exception as e:
raise IOError(e.message + 'happens at %s'%arg1)