Извлечь информацию трассировки из объекта исключения

111

Учитывая объект Exception (неизвестного происхождения), есть ли способ получить его трассировку? У меня такой код:

def stuff():
   try:
       .....
       return useful
   except Exception as e:
       return e

result = stuff()
if isinstance(result, Exception):
    result.traceback <-- How?

Как я могу извлечь трассировку из объекта Exception, когда она у меня есть?

Georg
источник

Ответы:

92

Ответ на этот вопрос зависит от версии Python, которую вы используете.

В Python 3

Все просто: исключения имеют __traceback__атрибут, содержащий трассировку. Этот атрибут также доступен для записи и может быть удобно установлен с помощью with_tracebackметода исключений:

raise Exception("foo occurred").with_traceback(tracebackobj)

Эти функции минимально описаны в raiseдокументации.

Вся заслуга в этой части ответа принадлежит Виктору, который первым опубликовал эту информацию . Я включаю его сюда только потому, что этот ответ застрял наверху, а Python 3 становится все более распространенным.

В Python 2

Это досадно сложно. Проблема с трассировкой заключается в том, что у них есть ссылки на кадры стека, а кадры стека имеют ссылки на трассировки, которые имеют ссылки на кадры стека, которые имеют ссылки на ... вы поняли. Это вызывает проблемы для сборщика мусора. (Спасибо ecatmur за первое указание на это.)

Хороший способ решить эту проблему - хирургическим путем разорвать цикл после выхода из exceptпредложения, что и делает Python 3. Решение Python 2 намного уродливее: вам предоставляется специальная функция sys.exc_info(), которая работает только внутри except предложения . Он возвращает кортеж, содержащий исключение, тип исключения и трассировку для любого исключения, которое в настоящее время обрабатывается.

Итак, если вы находитесь внутри exceptпредложения, вы можете использовать вывод sys.exc_info()вместе с tracebackмодулем для выполнения различных полезных вещей:

>>> import sys, traceback
>>> def raise_exception():
...     try:
...         raise Exception
...     except Exception:
...         ex_type, ex, tb = sys.exc_info()
...         traceback.print_tb(tb)
...     finally:
...         del tb
... 
>>> raise_exception()
  File "<stdin>", line 3, in raise_exception

Но, как указывает ваше редактирование, вы пытаетесь получить трассировку, которая была бы напечатана, если бы ваше исключение не было обработано, после того, как оно уже было обработано. Это гораздо более сложный вопрос. К сожалению, sys.exc_infoвозвращается, (None, None, None)если исключение не обрабатывается. Другие связанные sysатрибуты тоже не помогают. sys.exc_tracebackустарел и не определен, если исключение не обрабатывается; sys.last_tracebackкажется идеальным, но, похоже, он определяется только во время интерактивных сеансов.

Если вы можете контролировать, как возникает исключение, вы можете использовать inspectи настраиваемое исключение для хранения некоторой информации. Но я не совсем уверен, как это сработает.

По правде говоря, перехват и возврат исключения - дело необычное. Это может быть признаком того, что вам все равно нужно провести рефакторинг.

отправитель
источник
Я согласен с тем, что возвращение исключений в какой- то степени нетрадиционно, но посмотрите на мой другой вопрос, чтобы понять какое-то обоснование этого.
georg
@ thg435, хорошо, тогда в этом больше смысла. Рассмотрим мое вышеупомянутое решение, использующее sys.exc_infoв сочетании с подходом обратного вызова, который я предлагаю по вашему другому вопросу.
senderle
Дополнительная информация (ее очень мало) об объектах трассировки : docs.python.org/3/library/types.html#types.TracebackType docs.python.org/3/reference/datamodel.html#traceback-objects
0xfede7c8,
69

Начиная с Python 3.0 [PEP 3109] встроенный класс Exceptionимеет __traceback__атрибут, который содержит traceback object(с Python 3.2.3):

>>> try:
...     raise Exception()
... except Exception as e:
...     tb = e.__traceback__
...
>>> tb
<traceback object at 0x00000000022A9208>

Проблема в том, что после некоторого времени в Google__traceback__ я нашел только несколько статей, но ни одна из них не описывает, стоит ли вам (не) использовать __traceback__.

Однако в документации Python 3raise говорится, что:

Объект трассировки обычно создается автоматически при возникновении исключения и прикрепляется к нему в качестве __traceback__атрибута, который доступен для записи.

Так что я предполагаю, что это предназначено для использования.

Vyktor
источник
4
Да, это предназначено для использования. Из Что нового в Python 3.0 »PEP 3134: объекты исключений теперь сохраняют свою обратную трассировку как атрибут трассировки . Это означает, что объект исключения теперь содержит всю информацию, относящуюся к исключению, и меньше причин использовать sys.exc_info () ( хотя последний не удален) ".
Maciej Szpakowski
Я действительно не понимаю, почему этот ответ такой неуверенный и двусмысленный. Это задокументированная собственность; почему бы это не было «предназначено для использования»?
Марк Эмери
2
@MarkAmery Возможно, __в названии указано, что это деталь реализации, а не публичное свойство?
Базовый
4
@Basic это не то, что здесь указано. Обычно в Python __fooэто частный метод, но __foo__(также с завершающими символами подчеркивания) это «волшебный» метод (а не частный).
Марк Эмери
1
К вашему сведению, __traceback__атрибут на 100% безопасен для использования, как вам нравится, без последствий для сборки мусора. Трудно сказать это из документации, но ecatmur нашел веские доказательства .
senderle
39

Способ получить трассировку в виде строки из объекта исключения в Python 3:

import traceback

# `e` is an exception object that you get from somewhere
traceback_str = ''.join(traceback.format_tb(e.__traceback__))

traceback.format_tb(...)возвращает список строк. ''.join(...)объединяет их вместе. Для получения дополнительной информации посетите: https://docs.python.org/3/library/traceback.html#traceback.format_tb.

Хьеу
источник
21

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

>>> try:
...     print(1/0)
... except Exception as e:
...     exc = e
...
>>> exc
ZeroDivisionError('division by zero')
>>> tb_str = traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__)
>>> tb_str
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: division by zero\n']
>>> print("".join(tb_str))
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

Если вы используете format_tbприведенные выше ответы, вы получите меньше информации:

>>> tb_str = "".join(traceback.format_tb(exc.__traceback__))
>>> print("".join(tb_str))
  File "<stdin>", line 2, in <module>
Даниэль Портеус
источник
4
В заключение! Это должен быть главный ответ. Спасибо, Даниэль!
Дэни
3
Ах, я потратил последние 20 минут, пытаясь понять это, прежде чем я нашел это :-) etype=type(exc)теперь можно опустить, кстати: «Изменено в версии 3.5: аргумент etype игнорируется и выводится из типа значения». docs.python.org/3.7/library/… Протестировано в Python 3.7.3.
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
8

Есть очень веская причина, по которой трассировка не сохраняется в исключении; поскольку трассировка содержит ссылки на локальные переменные своего стека, это приведет к циклической ссылке и (временной) утечке памяти до тех пор, пока не сработает циклический сборщик мусора (вот почему никогда не следует сохранять трассировку в локальной переменной ).

Единственное, о чем я могу думать, - это использовать stuffглобальные переменные monkeypatch , чтобы, когда он думает, что ловит, Exceptionон на самом деле перехватывает специализированный тип, и исключение распространяется на вас как вызывающего:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
    stuff()
except Exception:
    import sys
    print sys.exc_info()
ecatmur
источник
7
Это не верно. Python 3 помещает объект трассировки в исключение, так как e.__traceback__.
Гленн Мейнард
6
@GlennMaynard Python 3 решает проблему, удаляя цель исключения при выходе из exceptблока согласно PEP 3110 .
ecatmur