Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

PythonのException発生時のTracebackを綺麗に見る

- はじめに -

PythonOSSパッケージ等を利用していると、Exceptionが発生した際に表示されるTraceback(正確にはスタックトレース)がかなり長い場合がある。

例えば、以下の簡易なコード実行で表示されるTracebackの行数は30近くなる。

import pandas as pd
df = pd.DataFrame(dict(a=[1,2,3]))
df['b']

引用 : python - Shorten large stack traces when using libraries - Stack Overflow


より複雑なプログラムにおいては、この比ではない。
にも関わらず、記述ミスのようにTraceback上位部にエラーの重要な内容がある場合もあれば、パッケージ内部のValidationで下位部が重要な場合もある。

得てしてPython開発環境として利用されるxonsh等の対話コンソール上やJupyter notebook等で100行近いエラーが出てきた時は、少なからず気持ちが折れる。

これらは、様々な言語の資産の利用などから引き起こされる長さである。
また設計上、過度なWrappingが行われた結果長くなっている場合も多々ある。

本記事は、主にPython3系において、Exceptionをcatchした時のTracebackを出来る限り綺麗に表示させ、あくまで開発のために人に優しい環境にするためのTipsやパッケージをまとめるものである。


※ 長く書いたが要約すると「xonshで作業してる時にクソ長いエラーが出てきてコンソール汚染されて腹立つから何とかしたい」と思っていたが調べてるうちに知見が溜まったのでまとめるという話


結論から言うとbacktraceかTBVaccineが良さそう。
 
もくじ:

 

- Tracebackの表示 -

そもそもPythonにおけるStackTraceは、Exception発生時にsys.last_tracebackに変数にtracebackオブジェクトとして格納される。

その中身をよしなに参照するためにsys.exc_info()が用意されている。
sys.exc_info()は返り値が(value, type, traceback)となっている長さ3のtupleなので、それらをtraceback.format_hogeに投げて以下のように直接中身を参照する事ができる。
(もちろんこんな実装を実際にしている人は居ないと思うが)

import sys
import traceback

try:
    x = 1 / 0    # ゼロ除算
except Exception as e:
    t, v, tb = sys.exc_info()
    print(traceback.format_exception(t,v,tb))
    print(traceback.format_tb(e.__traceback__))

# >>> ['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: division by zero\n']
# >>> ['  File "<stdin>", line 2, in <module>\n']


tracebackモジュールには、traceback.format_exctraceback.print_excが用意されているので実際はこちらで十分である。

import traceback

try:
    x = 1 / 0    # ゼロ除算
except:
    print(traceback.format_exc())    # いつものTracebackが表示される
    traceback.print_exc()                 # これでも同じ

 
参考 : 29.9. traceback — Print or retrieve a stack traceback — Python 3.6.4 documentation


 

tracebacklimitの利用

Pythonでは、sys.tracebacklimitでトレースバック情報のレベル値を設定できる(0〜1000)。

そして0から1000と書いたが、この機能は既知のバグとしてPython 3.xで機能しない。
Issue 12276: 3.x ignores sys.tracebacklimit=0 - Python tracker

一応Noneにすることで、値とエラー内容だけ出せる。

import sys

sys.tracebacklimit=None
x = 1 / 0

# >>> ZeroDivisionError: division by zero

これなら str(Exception) で十分。
2系を使っていないならあまり恩恵が得られない。

また、traceback.format_exc(limit=1)等とした場合も同様に見える。


参考 : 29.1. sys — System-specific parameters and functions — Python 3.6.4 documentation

 

ColoredTracebackでシンタックスハイライト

Tracebackの表示にカラーリングするパッケージがある。

github.com

導入はpip

pip install colored-traceback
pip install colorama    # Windows環境下の場合

基本的には import colored_traceback.always としておけば良い

f:id:vaaaaaanquish:20171214053335p:plain


基本的にSyntaxはIPythonならサポートしてくれてるので、主にコンソールで作業する時用に。

 

Pygmentsでシンタックスハイライト

上記と同じ事がPygmentsでもできる(こちらの方が一般的か)。
Available lexers — Pygments

以下のようにsys.excepthookをOverrideするための関数を作ってやればよい。

import sys
import traceback
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import TerminalFormatter

def myexcepthook(type, value, tb):
    tbtext = ''.join(traceback.format_exception(type, value, tb))
    lexer = get_lexer_by_name("pytb", stripall=True)
    formatter = TerminalFormatter()
    sys.stderr.write(highlight(tbtext, lexer, formatter))

sys.excepthook = myexcepthook

エラーがカラーリングされて見やすくなる。


   

- パッケージの利用 -

StackTrace表示を見やすくするための関連パッケージを示す。
backtraceが短く表示するやつで、それ以降は基本的に詳細表示のパッケージにあたる。

 

backtrace

StackTraceを短くしてくれるパッケージ。
上記colored-tracebackを使ってカラーリングもされる。

github.com

導入はpipで。Winの場合は上記colored-tracebackのcoloramaを先にinstallしておくと吉。

pip install backtrace


「はじめに」に記載のコードを実行してみる。

import pandas as pd
import backtrace

backtrace.hook(
    reverse=True,         # 逆順
    strip_path=True    # ファイル名のみ
)

df = pd.DataFrame(dict(a=[1,2,3]))
df['b']

f:id:vaaaaaanquish:20171214060800p:plain

これくらい分かれば何とかなるなって気もしなくもない。
何より慣れたpandasの30行近いエラーがこれに収まるなら良い。
基本的にはこれで作業して、backtrace.unhook() するのが良さそう。


また、sys.exc_info()の返り値を渡す形にすればコンソール、IPython上でも利用できる

import pandas as pd
import backtrace
import sys

try:
    df = pd.DataFrame(dict(a=[1,2,3]))
    df['b']
except:
    tpe, v, tb = sys.exc_info()
    backtrace.hook(reverse=True, strip_path=True, tb=tb, tpe=tpe, value=v)

ただし、中身がcoloramaでカラーリングしてるので色は変わらない。
この辺colored-tracebackに修正していけばかなり使えそう。

 

better-exceptions

Exceptionを見やすくするパッケージ。

github.com

エラー発生時に変数に格納されている値やClassをちゃんと出してくれて見やすい(?)
https://github.com/Qix-/better-exceptions/raw/master/screenshot.png


導入は以下

pip install better_exceptions

export BETTER_EXCEPTIONS=1  # Linux / OSX
setx BETTER_EXCEPTIONS 1       # Windows


試しにこんな感じのをやってみる

import better_exceptions

def zero(x):
    y = x/0
    return y

t = zero(10)
print(t)

ちなみにスクリプト実行時のみなので、対話コンソール上やIPythonでは現状使えない。
f:id:vaaaaaanquish:20171214100150p:plain:w500
それっぽい

しかしここからpandasのExceptionの方に適応すると以下
f:id:vaaaaaanquish:20171214101256p:plain:w400
厳しい

複雑になるとちょっと厳しいものがある。

 

TBVaccine

上記better-exceptionsよりちょっと見やすいやつ

github.com

https://github.com/skorokithakis/tbvaccine/raw/master/misc/after-vars.png

導入はよしなにpipで入れてTBVACCINE変数を設定しておくか、明示的にimportする。

pip install tbvaccine
export TBVACCINE=1

 
上記Githubの画像では設定された変数の中身まで出してるけど、以下のようにshow_varsをFalseにしておけば出ない。

import tbvaccine
tbvaccine.add_hook(isolate=False, show_vars=False)
import pandas as pd

df = pd.DataFrame(dict(a=[1,2,3]))
df['b']

f:id:vaaaaaanquish:20171214111250p:plain
良い感じである。


上記したsysモジュールっぽくも使えるのも良いところ。

from tbvaccine import TBVaccine
try:
    x = 1 / 0
except:
    print(TBVaccine(isolate=False, show_vars=False).format_exc())

これならxonshでも動くし、良い感じかもしれないと思っている。
IPythonでも動くし良さ。
 
 

その他調べたやつ

tracebackturbo

雰囲気はTBVaccineっぽい。変数の中身まで見たい時と見たくない時があるよなあって思うけど消せなさそう。
github.com
3系はこっち :
GitHub - cxcv/python-tracebackturbo3: A drop-in replacement for the python3 traceback module that enables dumping of the local variable scope aside normal stack traces.

 

git-stacktrace

pinterestのgitリポジトリはじめて見た。
stacktraceと一緒に、問題が発生した箇所のGitの修正履歴をコミット単位で表示してくれる。
github.com
毎日Git使ってたら便利なのかも。
今回は趣向と外れすぎたのでノータッチ。

 

python-tblib

tracebackをPickleで固めてraiseしていくやつ。
multiprocessで処理をした時のtracebackが見やすくなる。
github.com
今回は趣向と外れすぎたのでノータッチ。いつか触る。

 

Skip-Traceback(jupyter)

jupyterの拡張でトレースバックを表示せずエラーの種類とメッセージのみ表示するやつ。
github.com
今回は趣向と外れすぎたのでノータッチ。

IPythonならまだmagic commandのdebugやpdbデバッグする方が普通に良い気がする。
JupyterまたはiPython Notebookでデバッグをする方法 - Qiita
IPython の豊富な機能を使いこなす (2) - Qiita

IPythonならultratb.ColorTBも使えるけどなあ…
Module: core.ultratb — IPython 3.2.1 documentation


 

- おわりに -

結論私の求めていた、短く良い感じにExceptionを表示する方法はbacktraceかTBVaccine辺りだろう。

個人的にbacktraceの表示形式が好きなので、ここを起点にxontribを作っていくぞという気持ち。

IPythonにも活用できたら、Jupyter notebookでミスって実行しちゃった時に出る長いエラーとおさらば出来る気がする…


 
//--- 以下参考 ---
29.9. traceback — Print or retrieve a stack traceback — Python 3.6.4 documentation
traceback — Exceptions and Stack Traces — PyMOTW 3
__exit__ must accept 3 arguments: type, value, traceback — Python Anti-Patterns documentation

PythonユーザのためのJupyter[実践]入門

PythonユーザのためのJupyter[実践]入門

blog.dscpl.com.au
d.hatena.ne.jp


 
追記:
xonshに導入する記事も書きました
vaaaaaanquish.hatenablog.com
なんとかしてJupyter notebookもbacktraceにできないかなあと思っています。