"Внутреннее исключение" (с обратной трассировкой) в Python?

мой фон находится на C#, и я только недавно начал программировать на Python. Когда возникает исключение, я обычно хочу обернуть его в другое исключение, которое добавляет больше информации, все еще показывая полную трассировку стека. Это довольно легко в C#, но как это сделать в Python?

например. в C# я бы сделал что-то вроде этого:

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)

...но это теряет след внутреннего исключение!

Edit: Я хотел бы видеть как сообщения об исключениях, так и трассировки стека и коррелировать их. То есть я хочу видеть в выходных данных, что исключение X произошло здесь, а затем исключение Y там - так же, как и в C#. Это возможно в Python 2.6? Похоже, лучшее, что я могу сделать до сих пор (на основе ответа Гленна Мейнарда), это:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]

Это включает в себя как сообщения, так и оба tracebacks, но не показывает, какое исключение произошло, где в обратном направлении.

8 ответов


это просто; передайте traceback в качестве третьего аргумента для поднятия.

import sys
class MyException(Exception): pass

try:
    raise TypeError("test")
except TypeError, e:
    raise MyException(), None, sys.exc_info()[2]

всегда делайте это при ловле одного исключения и повторном вызове другого.


в 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")

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. Вы должны импортировать .

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() называется, но сообщение об исключении теперь включает командную строку.


на 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

вы можете предотвратить печать обоих исключений, убив


Я не думаю, что вы можете сделать это в Python 2.X, но что-то похожее на эту функциональность в Python 3. От PEP 3134:

в сегодняшней реализации Python исключения состоят из трех детали: тип, значение, и вывод. Модуль индикатор 'sys' , предоставляет текущее исключение в трех параллельных переменных, exc_type, exc_value и exc_traceback, sys.exc_info() функция возвращает кортеж из этих трех части, и заявление "raise" имеет форма трех аргументов, принимающая эти три части. Манипулирующий исключения часто требуют прохождения этих трех вещей параллельно, что может быть утомительным и подверженным ошибкам. Кроме того, "кроме" оператор может предоставить доступ только к значению, а не к обратной трассировке. Добавление 'traceback' атрибут к значениям исключения делает все сведения об исключении, доступные из одного место.

сравнение с C#:

исключения в C# содержат свойство "InnerException" только для чтения, которое может указывать на другое исключение. Документации [10] говорит, что "Когда исключение X возникает как прямой результат предыдущего исключение Y, свойство InnerException X должно содержать a ссылка на Y. " Это свойство не устанавливается виртуальной машиной автоматически; скорее, все конструкторы исключений принимают необязательный 'свойство innerexception' аргумент, чтобы установить его явно. The'причина' выполняет та же цель, что и InnerException, но эта ОПТОСОЗ предлагает новую форму "поднять", а не расширять конструкторы всех исключений. C# также предоставляет метод GetBaseException, который переходит непосредственно к конец цепочки InnerException; этот ОПТОСОЗ не предлагает аналога.

обратите внимание также, что Java, Ruby и Perl 5 не поддерживают этот тип вещей любой. Опять цитата:

Что касается других языков, Java и Ruby оба отбрасывают оригинал исключение, когда другое исключение возникает в "catch" / "rescue" или 'finally'/' ensure ' пункт. Perl 5 не имеет встроенной структурированной структуры обработка исключений. Для Perl 6, RFC номер 88 [9] предлагает исключение механизм, который неявно сохраняет цепные исключения в массиве упомянутый.@@


вы могли бы использовать мой класс CausedException для цепочки исключений в Python 2.x (и даже в Python 3 это может быть полезно, если вы хотите дать более одного пойманного исключения в качестве причины для вновь созданного исключения). Может, это тебе поможет.


может быть, вы могли бы захватить соответствующую информацию и передать ее? Я думаю что-то типа:

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

предположим:

  • вам нужно решение, которое работает для Python 2 (для чистого Python 3 см. raise ... from решение)
  • просто хочу обогатить сообщение об ошибке, например, предоставляя некоторый дополнительный контекст
  • нужна полная трассировка стека

вы можете использовать простое решение из документов 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

Это похоже, ключевая часть-это упрощенное ключевое слово "raise", которое стоит отдельно. Это повторно вызовет исключение в блоке except.