Витягування інформації про зворотний зворотний зв'язок з об’єкта винятку


111

З огляду на об’єкт «Виняток» (невідомого походження), чи є спосіб його відстеження? У мене такий код:

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

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

Як я можу витягнути прослідкування з об’єкта Exception, як тільки він його мати?

Відповіді:


92

Відповідь на це питання залежить від версії Python, яку ви використовуєте.

У Python 3

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

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

Ці функції мінімально описані як частина raiseдокументації.

Вся заслуга за цю частину відповіді повинна мати Виктор, який першим опублікував цю інформацію . Я включаю його сюди лише тому, що ця відповідь застрягла вгорі, а Python 3 стає все більш поширеним.

У Python 2

Це прикро складно. Проблема із зворотними зворотами полягає в тому, що вони мають посилання на фрейми стеків, а фрейми стеків мають посилання на зворотні сигнали, які мають посилання на фрейми стеків, на які є посилання на ... Ви отримуєте ідею. Це спричиняє проблеми зі збирачем сміття. (Дякую Екатмуру, що вперше вказав на це.)

Хорошим способом вирішити це було б хірургічним шляхом розірвати цикл після відмови від цього 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

Більше інформації (про дуже мало об’єктів) про об'єкти trackback : 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>

Проблема полягає в тому, що після Googling__traceback__ деякий час я знайшов лише декілька статей, але жодна з них не описує, чи потрібно (не) використовувати __traceback__.

Однак у документації Python 3raise зазначено, що:

Об'єкт відстеження зазвичай створюється автоматично, коли піднято виняток і додається до нього як __traceback__атрибут, який можна записати .

Тому я припускаю, що це призначено для використання.


4
Так, він призначений для використання. Із нового в Python 3.0 "PEP 3134: Об'єкти винятку тепер зберігають свій слід назад як атрибут traceback . Це означає, що об'єкт виключення тепер містить усю інформацію, що відноситься до винятку, і є менше причин використовувати sys.exc_info () ( хоча останнє не видалено). "
Мацей Шпаковський

Я не дуже розумію, чому ця відповідь настільки нерішуча та однозначна. Це задокументоване майно; чому б це не було "призначене для використання"?
Марк Амерді

2
@MarkAmery Можливо, __ім'я, яке вказує на те, що це деталі реалізації, а не публічна власність?
Основні

4
@Basic це не те, що тут зазначено. Зазвичай у Python __foo- це приватний метод, але __foo__(з кінцевими підкресленнями) це "магічний" метод (а не приватний).
Марк Амері

1
FYI, цей __traceback__атрибут на 100% безпечний для використання, як вам завгодно, без наслідків для GC. Важко сказати, що це з документації, але екатмур знайшов важкі докази .
senderle

38

Спосіб отримання відстеження у вигляді рядка від об’єкта виключення в 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)можна пропустити btw: "Змінено у версії 3.5: Аргумент etype ігнорується та виводиться з типу значення." docs.python.org/3.7/library/… Перевірено на Python 3.7.3.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

8

Є дуже вагома причина, що відслідковування не зберігається за винятком; через те, що у зворотному відслідженні є посилання на локальних пристроїв стека, це призведе до циркулярної посилання та (тимчасової) пам’яті, поки не запуститься круговий GC. (Ось чому ніколи не слід зберігати прослідкування у локальній змінній .)

Про єдине, про що я міг би придумати, - це ти будеш вибирати stuffглобальні глобальні програми, щоб, коли він думає, що це ловити, Exceptionвін фактично ловив спеціалізований тип, а виняток поширюється на тебе як на виклику:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
    stuff()
except Exception:
    import sys
    print sys.exc_info()

7
Це неправильно. Python 3 ставить об'єкт traceback у виняток, як e.__traceback__.
Гленн Мейнард

6
@GlennMaynard Python 3 вирішує проблему, видаляючи ціль виключення під час виходу з exceptблоку, відповідно до PEP 3110 .
екатмур
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.