Extrahieren Sie Traceback-Informationen aus einem Ausnahmeobjekt

111

Gibt es bei einem Exception-Objekt (unbekannter Herkunft) eine Möglichkeit, seinen Traceback zu erhalten? Ich habe folgenden Code:

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

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

Wie kann ich den Traceback aus dem Exception-Objekt extrahieren, sobald ich ihn habe?

georg
quelle

Antworten:

92

Die Antwort auf diese Frage hängt von der verwendeten Python-Version ab.

In Python 3

Es ist ganz einfach: Ausnahmen sind mit einem __traceback__Attribut ausgestattet, das den Traceback enthält. Dieses Attribut ist auch beschreibbar und kann bequem mithilfe der with_tracebackAusnahmemethode festgelegt werden:

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

Diese Funktionen werden in der raiseDokumentation nur minimal beschrieben .

Alle Gutschriften für diesen Teil der Antwort sollten an Vyctor gehen, der diese Informationen zuerst veröffentlicht hat . Ich füge es hier nur ein, weil diese Antwort oben feststeckt und Python 3 immer häufiger wird.

In Python 2

Es ist ärgerlich komplex. Das Problem mit Tracebacks ist, dass sie Verweise auf Stapelrahmen haben und Stapelrahmen Verweise auf die Rückverfolgungen haben, die Verweise auf Stapelrahmen haben , die Verweise auf ... Sie haben die Idee. Dies verursacht Probleme für den Garbage Collector. (Vielen Dank an ecatmur für den ersten Hinweis.)

Die gute Möglichkeit, dies zu lösen, besteht darin , den Zyklus nach dem Verlassen der exceptKlausel chirurgisch zu unterbrechen, wie es Python 3 tut. Die Python 2-Lösung ist viel hässlicher: Sie erhalten eine Ad-hoc-Funktion sys.exc_info(), die nur innerhalb der except Klausel funktioniert . Es gibt ein Tupel zurück, das die Ausnahme, den Ausnahmetyp und den Traceback für jede Ausnahme enthält, die gerade behandelt wird.

Wenn Sie sich also innerhalb der exceptKlausel befinden, können Sie die Ausgabe von sys.exc_info()zusammen mit dem tracebackModul verwenden, um verschiedene nützliche Dinge zu tun:

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

Wie aus Ihrer Bearbeitung hervorgeht, versuchen Sie jedoch, den Traceback abzurufen, der gedruckt worden wäre, wenn Ihre Ausnahme nicht behandelt worden wäre, nachdem sie bereits behandelt wurde. Das ist eine viel schwierigere Frage. Leider sys.exc_infogibt , (None, None, None)wenn keine Ausnahme behandelt wird. Andere verwandte sysAttribute helfen auch nicht. sys.exc_tracebackist veraltet und undefiniert, wenn keine Ausnahme behandelt wird; sys.last_tracebackscheint perfekt zu sein, scheint aber nur während interaktiver Sitzungen definiert zu werden.

Wenn Sie steuern können, wie die Ausnahme ausgelöst wird, können Sie möglicherweise inspecteine benutzerdefinierte Ausnahme verwenden , um einige der Informationen zu speichern. Aber ich bin mir nicht ganz sicher, wie das funktionieren würde.

Um die Wahrheit zu sagen, ist es ungewöhnlich, eine Ausnahme zu fangen und zurückzugeben. Dies könnte ein Zeichen dafür sein, dass Sie ohnehin umgestalten müssen.

senderle
quelle
Ich stimme zu, dass die Rückgabe von Ausnahmen irgendwie unkonventionell ist, sehe aber meine andere Frage als Begründung dafür.
Georg
@ thg435, ok, das macht dann mehr Sinn. Betrachten Sie meine obige Lösung sys.exc_infoin Verbindung mit dem Rückrufansatz, den ich für Ihre andere Frage vorschlage.
senderle
Weitere Informationen (es gibt nur sehr wenige) zu Traceback-Objekten: docs.python.org/3/library/types.html#types.TracebackType docs.python.org/3/reference/datamodel.html#traceback-objects
0xfede7c8
69

Seit Python 3.0 [PEP 3109] hat die eingebaute Klasse Exceptionein __traceback__Attribut, das a enthält traceback object(mit Python 3.2.3):

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

Das Problem ist, dass ich__traceback__ nach einer Weile des Googelns nur wenige Artikel gefunden habe, aber keiner beschreibt, ob oder warum Sie verwenden sollten (nicht) __traceback__.

In der Python 3-Dokumentation fürraise heißt es jedoch:

Ein Traceback-Objekt wird normalerweise automatisch erstellt, wenn eine Ausnahme ausgelöst und als __traceback__beschreibbares Attribut an dieses angehängt wird.

Ich gehe also davon aus, dass es verwendet werden soll.

Vyktor
quelle
4
Ja, es soll verwendet werden. Von Was ist neu in Python 3.0 „PEP 3134: Ausnahme speichern Objekte jetzt ihre Zurückverfolgungs als Traceback - Attribut Das bedeutet , dass ein Ausnahmeobjekt alle jetzt enthält die Informationen zu einer Ausnahme gehören, und es gibt wenige Gründe sys.exc_info () zu verwenden (. obwohl letzteres nicht entfernt wird). "
Maciej Szpakowski
Ich verstehe nicht wirklich, warum diese Antwort so zögerlich und zweideutig ist. Es ist eine dokumentierte Eigenschaft; warum sollte es nicht "verwendet werden"?
Mark Amery
2
@MarkAmery Möglicherweise der __Name, der angibt, dass es sich um ein Implementierungsdetail handelt, nicht um eine öffentliche Eigenschaft?
Basic
4
@Basic das ist nicht das, was es hier anzeigt. Herkömmlicherweise ist Python __fooeine private Methode, aber __foo__(auch mit nachgestellten Unterstrichen) eine "magische" Methode (und nicht privat).
Mark Amery
1
Zu Ihrer Information, das __traceback__Attribut ist 100% sicher zu verwenden, wie Sie möchten, ohne GC-Auswirkungen. Es ist schwer, das aus der Dokumentation zu ersehen, aber ecatmur fand harte Beweise .
senderle
38

Eine Möglichkeit, Traceback als Zeichenfolge von einem Ausnahmeobjekt in Python 3 abzurufen:

import traceback

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

traceback.format_tb(...)Gibt eine Liste von Zeichenfolgen zurück. ''.join(...)verbindet sie zusammen. Weitere Informationen finden Sie unter: https://docs.python.org/3/library/traceback.html#traceback.format_tb

Hieu
quelle
21

Abgesehen davon möchten Sie Folgendes, wenn Sie tatsächlich den vollständigen Traceback erhalten möchten, wie er auf Ihrem Terminal gedruckt wird.

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

Wenn Sie die format_tbobigen Antworten verwenden, erhalten Sie weniger Informationen:

>>> tb_str = "".join(traceback.format_tb(exc.__traceback__))
>>> print("".join(tb_str))
  File "<stdin>", line 2, in <module>
Daniel Porteous
quelle
4
Schließlich! Dies sollte die beste Antwort sein. Danke, Daniel!
Dany
3
Argh, ich hatte die letzten 20 Minuten damit verbracht, dies herauszufinden, bevor ich dies fand :-) etype=type(exc)kann jetzt weggelassen werden: "In Version 3.5 geändert: Das etype-Argument wird ignoriert und aus dem Werttyp abgeleitet." docs.python.org/3.7/library/… Getestet in Python 3.7.3.
Ciro Santilli 法轮功 冠状 病 六四 事件 18
8

Es gibt einen sehr guten Grund, warum der Traceback nicht in der Ausnahme gespeichert ist. Da der Traceback Verweise auf die Lokalitäten seines Stacks enthält, würde dies zu einem Zirkelverweis und einem (vorübergehenden) Speicherverlust führen, bis der zirkuläre GC aktiviert wird. (Aus diesem Grund sollten Sie den Traceback niemals in einer lokalen Variablen speichern .)

Das Einzige, stuffworan ich denken kann, ist, dass Sie die Globals von monkeypatch so einstellen, dass sie, wenn sie glauben, dass sie abfangen, Exceptiontatsächlich einen speziellen Typ abfangen und die Ausnahme sich an Sie als Anrufer weitergibt:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
    stuff()
except Exception:
    import sys
    print sys.exc_info()
ecatmur
quelle
7
Das ist falsch. Python 3 setzt das Traceback-Objekt in die Ausnahme als e.__traceback__.
Glenn Maynard
6
@GlennMaynard Python 3 behebt das Problem, indem das Ausnahmeziel beim Verlassen des exceptBlocks gemäß PEP 3110 gelöscht wird .
Ecatmur