web-dev-qa-db-ja.com

例外オブジェクトからトレースバック情報を抽出する

(起源不明の)Exceptionオブジェクトが与えられた場合、そのトレースバックを取得する方法はありますか?このようなコードがあります:

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

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

例外オブジェクトを取得したら、どのようにして例外オブジェクトからトレースバックを抽出できますか?

93
georg

この質問に対する答えは、使用しているPythonのバージョンによって異なります。

Python 3

簡単です。例外には、トレースバックを含む__traceback__属性が装備されています。この属性も書き込み可能であり、例外のwith_tracebackメソッドを使用して簡単に設定できます。

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

これらの機能は、 raise ドキュメントの一部として最小限説明されています。

回答のこの部分のすべての功績は、Vyctorに委ねるべきです。Vyctorは 最初にこの情報を投稿しました です。この答えが一番上に留まっており、Python 3がより一般的になっているため、ここに含めています。

Python 2

煩わしいほど複雑です。トレースバックの問題は、スタックフレームへの参照があり、スタックフレームにスタックフレームへの参照があるトレースバックへの参照があることです ...への参照がある アイデアが得られます。これにより、ガベージコレクターで問題が発生します。 (最初にこれを指摘してくれた ecatmur に感謝します。)

これを解決する良い方法は、Python 3が行うexcept句を残した後、外科的に サイクルを壊す にすることです。 Python 2ソリューションははるかにいです:アドホック関数、 sys.exc_info() が提供されます。これは内部でのみ動作しますexceptclause。例外、例外タイプ、および現在処理中の例外のトレースバックを含むTupleを返します。

したがって、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

しかし、編集が示すように、例外が処理されていない場合、alreadyが処理された後、wouldが出力されたというトレースバックを取得しようとしています。それははるかに難しい質問です。残念ながら、例外が処理されていない場合、sys.exc_info(None, None, None)を返します。他の関連sys属性も役に立たない。 sys.exc_tracebackは非推奨であり、例外が処理されていない場合は未定義です。 sys.last_tracebackは完璧に思えますが、インタラクティブセッション中にのみ定義されるようです。

例外の発生方法を制御できる場合は、 inspect および カスタム例外 を使用して、一部の情報を保存できます。しかし、それがどのように機能するかは完全にはわかりません。

実を言うと、例外をキャッチして返すことは、やるべきことの一種です。これは、とにかくリファクタリングする必要がある兆候かもしれません。

76
senderle

以来 Python 3.0[PEP 3109] 組み込みクラス Exception には、__traceback__(Python 3.2.3を含む)を含むtraceback object属性があります。

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

問題は、 グーグル__traceback__ の後しばらくの間、数件の記事しか見つかりませんでしたが、__traceback__を使用する(しない)かどうか、またはその理由を説明していないことです。

ただし、 raiseのPython 3ドキュメント には次のように書かれています:

トレースバックオブジェクトは通常、例外が発生したときに自動的に作成され、書き込み可能な__traceback__属性としてオブジェクトにアタッチされます。

だから私はそれが使用されることを意図していると思います。

59
Vyktor

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_exc

19
Hieu

余談ですが、実際に端末に出力されるのと同じように完全なトレースバックを取得したい場合は、次のようにします。

>>> 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>
10
Daniel Porteous

トレースバックが例外に保存されない非常に良い理由があります。トレースバックはそのスタックのローカルへの参照を保持するため、循環GCが起動するまで循環参照と(一時的な)メモリリークが発生します(これが、トレースバックを保存しない ローカル変数 。)

私が考えることができる唯一のものは、stuffのグローバルをmonkeypatchし、Exceptionをキャッチしていると思うと、実際に特殊な型をキャッチし、例外が呼び出し元としてあなたに伝播することです:

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