"Внутреннее исключение" (с отслеживанием) в Python?
Мой фон находится на С#, и я только недавно начал программирование на Python. Когда возникает исключение, я обычно хочу обернуть его в другое исключение, которое добавляет дополнительную информацию, сохраняя при этом полную трассировку стека. Это довольно легко в С#, но как это сделать в Python?
Eg. в С# я бы сделал что-то вроде этого:
try
{
ProcessFile(filePath);
}
catch (Exception ex)
{
throw new ApplicationException("Failed to process file " + filePath, ex);
}
В Python я могу сделать что-то подобное:
try:
ProcessFile(filePath)
except Exception as e:
raise Exception('Failed to process file ' + filePath, e)
... но это теряет трассировку внутреннего исключения!
Изменить: Я бы хотел видеть как сообщения исключения, так и обе трассировки стека и сопоставить их. То есть, я хочу видеть на выходе, что исключение X произошло здесь, а затем исключение Y там - то же, что и в С#. Возможно ли это в Python 2.6? Похоже, лучшее, что я могу сделать до сих пор (на основе ответа Гленна Мейнарда):
try:
ProcessFile(filePath)
except Exception as e:
raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]
Это включает как сообщения, так и обе трассировки, но не показывает, какое исключение произошло в трассировке.
Ответы
Ответ 1
Python 2
Это просто; передать в качестве третьего аргумента трассировку.
import sys
class MyException(Exception): pass
try:
raise TypeError("test")
except TypeError, e:
raise MyException(), None, sys.exc_info()[2]
Всегда делайте это, когда ловите одно исключение и повторно вызываете другое.
Ответ 2
Python 3
В Python 3 вы можете сделать следующее:
try:
raise MyExceptionToBeWrapped("I have twisted my ankle")
except MyExceptionToBeWrapped as e:
raise MyWrapperException("I'm not in a good shape") from e
Это будет производить что-то вроде этого:
Traceback (most recent call last):
...
MyExceptionToBeWrapped: ("I have twisted my ankle")
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
MyWrapperException: ("I'm not in a good shape")
Ответ 3
В Python 3 есть исключение raise
... from
предложения до цепочки. Ответ Гленна хорош для Python 2.7, но он использует только оригинальную трассировку исключений и выбрасывает сообщение об ошибке и другие детали. Вот несколько примеров в Python 2.7, которые добавляют контекстную информацию из текущей области в исходное сообщение об ошибке исключения, но сохраняют другие детали без изменений.
Известный тип исключения
try:
sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
self.user_id = sock_common.login(self.dbname, username, self.pwd)
except IOError:
_, ex, traceback = sys.exc_info()
message = "Connecting to '%s': %s." % (config['connection'],
ex.strerror)
raise IOError, (ex.errno, message), traceback
Эта разновидность оператора raise
принимает тип исключения в качестве первого выражения, аргументы конструктора класса исключения в кортеже в качестве второго выражения и трассировку в качестве третьего выражения. Если вы работаете раньше, чем Python 2.2, смотрите предупреждения в sys.exc_info()
.
Любой тип исключения
Вот еще один пример более общего назначения, если вы не знаете, какие исключения может потребоваться вашему коду. Недостатком является то, что он теряет тип исключения и просто вызывает RuntimeError. Вы должны импортировать модуль traceback
.
except Exception:
extype, ex, tb = sys.exc_info()
formatted = traceback.format_exception_only(extype, ex)[-1]
message = "Importing row %d, %s" % (rownum, formatted)
raise RuntimeError, message, tb
Изменить сообщение
Вот еще один вариант, если тип исключения позволит вам добавить к нему контекст. Вы можете изменить сообщение об исключении, а затем пересмотреть его.
import subprocess
try:
final_args = ['lsx', '/home']
s = subprocess.check_output(final_args)
except OSError as ex:
ex.strerror += ' for command {}'.format(final_args)
raise
Это генерирует следующую трассировку стека:
Traceback (most recent call last):
File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in <module>
s = subprocess.check_output(final_args)
File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
process = Popen(stdout=PIPE, *popenargs, **kwargs)
File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
errread, errwrite)
File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
raise child_exception
OSError: [Errno 2] No such file or directory for command ['lsx', '/home']
Вы можете видеть, что он показывает строку, где check_output()
, но сообщение об исключении теперь включает командную строку.
Ответ 4
В Python 3.x:
raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)
или просто
except Exception:
raise MyException()
который будет распространять MyException
, но печатать оба исключения, если он не будет обработан.
В Python 2.x:
raise Exception, 'Failed to process file ' + filePath, e
Вы можете предотвратить печать обоих исключений, убив атрибут __context__
. Здесь я пишу диспетчер контекста, используя это, чтобы поймать и изменить ваше исключение на лету:
(см. http://docs.python.org/3.1/library/stdtypes.html для раскрытия того, как они работают)
try: # Wrap the whole program into the block that will kill __context__.
class Catcher(Exception):
'''This context manager reraises an exception under a different name.'''
def __init__(self, name):
super().__init__('Failed to process code in {!r}'.format(name))
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
self.__traceback__ = exc_tb
raise self
...
with Catcher('class definition'):
class a:
def spam(self):
# not really pass, but you get the idea
pass
lut = [1,
3,
17,
[12,34],
5,
_spam]
assert a().lut[-1] == a.spam
...
except Catcher as e:
e.__context__ = None
raise
Ответ 5
Я не думаю, что вы можете сделать это в Python 2.x, но что-то похожее на эту функциональность является частью Python 3. Из PEP 3134:
В сегодняшней реализации Python исключения состоят из трех детали: тип, значение и трассировка. Модуль "sys", предоставляет текущее исключение в трех параллельных переменных, exc_type, exc_value и exc_traceback, функция sys.exc_info() возвращает кортеж этих трех частей, а оператор "рейз" имеет трехфакторная форма, принимающая эти три части. Манипулирование исключения часто требуют прохождения этих трех вещей параллельно, который может быть утомительным и подверженным ошибкам. Кроме того, "кроме" оператор может предоставлять доступ только к значению, а не к трассировке. Добавление атрибута трассировка 'к значениям исключений делает все информация об исключении, доступная из одного места.
Сравнение с С#:
Исключения в С# содержат свойство InnerException, доступное только для чтения, которое может указывать на другое исключение. В его документации [10] говорится, что "Когда исключение X выбрано как прямой результат предыдущего исключение Y, свойство InnerException для X должно содержать ссылка на Y." Это свойство не задано виртуальной машиной автоматически; скорее, все конструкторы исключений принимают необязательное "внутреннее исключение", аргумент, чтобы установить его явно. Атрибут причина выполняется та же самая цель, что и InnerException, но этот PEP предлагает новую форму "повышения", а не расширения конструкторов всех исключений. С# также предоставляет метод GetBaseException, который переходит непосредственно к конец цепи InnerException; этот PEP не предлагает аналогов.
Заметьте также, что Java, Ruby и Perl 5 тоже не поддерживают этот тип вещей. Цитирование снова:
Как и для других языков, Java и Ruby отбрасывают оригинал исключение, когда возникает другое исключение в "catch" / "rescue" или 'finally'/'обеспечить'. В Perl 5 отсутствует встроенная структурированная Обработка исключений. Для Perl 6 RFC номер 88 [9] предлагает исключение механизм, который неявно сохраняет замкнутые исключения в массиве named @@.
Ответ 6
Вы можете использовать мой класс CausedException для привязки исключений в Python 2.x(и даже в Python 3 он может быть полезен, если вы хотите чтобы дать больше одного пойманного исключения в качестве причины для вновь возникшего исключения). Может быть, это может вам помочь.
Ответ 7
Может быть, вы могли бы получить соответствующую информацию и передать ее? Я думаю что-то вроде:
import traceback
import sys
import StringIO
class ApplicationError:
def __init__(self, value, e):
s = StringIO.StringIO()
traceback.print_exc(file=s)
self.value = (value, s.getvalue())
def __str__(self):
return repr(self.value)
try:
try:
a = 1/0
except Exception, e:
raise ApplicationError("Failed to process file", e)
except Exception, e:
print e
Ответ 8
Предполагая, что:
- вам нужно решение, которое работает для Python 2 (для чистого Python 3 см. решение
raise ... from
)
- просто хотите обогатить сообщение об ошибке, например. предоставление дополнительного контекста
- нужна полная трассировка стека
вы можете использовать простое решение из docs https://docs.python.org/3/tutorial/errors.html#raising-exceptions:
try:
raise NameError('HiThere')
except NameError:
print 'An exception flew by!' # print or log, provide details about context
raise # reraise the original exception, keeping full stack trace
Выход:
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in ?
NameError: HiThere
Похоже, что ключевой частью является упрощенное ключевое слово "повышение", которое стоит отдельно. Это приведет к повторному воссозданию Исключения в исключающем блоке.
Ответ 9
Для максимальной совместимости между Python 2 и 3, вы можете использовать raise_from
в six
библиотеки. https://six.readthedocs.io/#six.raise_from. Вот ваш пример (слегка измененный для ясности):
import six
try:
ProcessFile(filePath)
except Exception as e:
six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)