【Tkinter】Tkinterでtraceback表示したい！

# はじめに
tracebackをmessageboxに表示したい！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2716347/1a92592d-71b0-2a25-f304-7affa3e75e3b.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2716347/5e361733-804e-f233-ba34-132f19699b3e.png)

Tkinterで作成したアプリケーションをexe化して完璧だと思った矢先、ユーザーによって予期せぬエラーを引き起こされる場合がある（知らんけど）

consoleウィンドウがあればtracebackを遡ってエラーを追求できるが、GUIのみの場合は何も起こらずエラーが出たのかすらわからない。

予測できるエラーに対してはtkinterの`messagebox.showerror()`をif文とかで仕込んでおけばよいが、すべてのエラーに対して仕込むのは至難の業。

今回は、こんな風にtracebackの一部を表示し、
consoleがなくても「エラーやで」とわかってもらう方法をメモします。


# 発想
基本的な考え方は次の通りである。
* `try-except`文を使ってエラーのときは`messagebox.showerror()`を表示する
* すべての動作に`try-except`文を仕込むのはタルいので、デコレーターを作成しておく
* `traceback`ライブラリを使ってエラー文を`str`でゲットする

# コード
GUIをに数字を入れるとその逆数を計算するプログラムです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2716347/c22d6e30-3b5e-627e-2931-a68687dda6ba.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2716347/4edfd943-c20a-6a3a-233a-b97d0a8baf81.png)



```python
import tkinter as tk
from tkinter import messagebox

import traceback
from functools import wraps

# =============================================================================
# デコレータの定義
# =============================================================================
def show_on_error(function):
    "関数実行中に例外が発生したら、showerrorするデコレータ"
    @wraps(function)
    def show_error(*args,**kwargs):
        try:
            function(*args,**kwargs)
        except Exception as e:
            print(traceback.format_exc())
            title = e.__class__.__name__
            message = traceback.format_exc(limit=0)
            messagebox.showerror(f"{title}",
                                 f"{message}")
    return show_error

# =============================================================================
# GUIから呼び出される関数
# =============================================================================
@show_on_error
def calc(x):
    "いかにもエラーが出そうな関数"
    y = 1/float(x)
    messagebox.showinfo("ans",y)
    return y

# =============================================================================
# メイン処理
# =============================================================================
def main():
    "GUI作成"
    # ウィジェット定義
    root = tk.Tk()
    frame = tk.Frame(root,
                      padx=5,
                      pady=5)
    entry = tk.Entry(frame,
                      width=50)
    button = tk.Button(frame,
                        width=10,
                        text="button",
                        command=lambda:calc(entry.get()))
    
    # ウィジェット配置
    entry.grid(row=0,column=0)
    button.grid(row=0,column=1)
    frame.grid(row=0,column=0)
    root.mainloop()
    
    return

if __name__ == "__main__":
    main()
```

# 解説
### モジュール
GUI作成に必要なモジュール
```python
import tkinter as tk
from tkinter import messagebox
```
エラー文を取得するモジュール
```python
import traceback
```

`関数.__name__`が正しく返ってくるようにするおまじない
```python
from functools import wraps
```

### デコレータ
デコレータ`show_on_error`を定義します。
* `def`文
`show_on_error`関数は引数に関数(`function`)取り、
`show_error`関数で`function`を実行する直前に`try-except`文をかませる
* `try`文
`function`を実行し、エラーがあったら`except`文に飛ぶ
* `except`文
まずはconsoleに`print(traceback.format_exc())`でtracebackメッセージを出力する(`print`を使わずに`traceback.print_exc()`でもOK)
`title = e.__class__.__name__`で例外クラスの名前を取得
`message = traceback.format_exc(limit=0)`でtracebackメッセージの最後の文を取得(limitの値を変えるとどこまで遡るかを変更できる)
`messagebox.showerror(f"{title}",f"{message}")`でmessageboxを表示する
* `return`文
`return show_error`を書かないと`calc()`を呼び出した際に`'NoneType' object is not callable`と怒られる

* `@wraps(function)`は`関数.__name__`が正しく返ってくるようにするおまじない

```python
def show_on_error(function):
    "関数実行中に例外が発生したら、showerrorするデコレータ"
    @wraps(function)
    def show_error(*args,**kwargs):
        try:
            function(*args,**kwargs)
        except Exception as e:
            print(traceback.format_exc())
            title = e.__class__.__name__
            message = traceback.format_exc(limit=0)
            messagebox.showerror(f"{title}",
                                 f"{message}")
    return show_error
```
### 関数をデコレート
`@show_on_error`で`calc()`関数をデコレート
`x=0`や`x="aaa"`を代入するとそれぞれ別のエラーがポップアップする。
エラーが出なければ逆数を表示する
```python
@show_on_error
def calc(x):
    "いかにもエラーが出そうな関数"
    y = 1/float(x)
    messagebox.showinfo("ans",y)
    return y
```
うまく行ったとき
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2716347/4edfd943-c20a-6a3a-233a-b97d0a8baf81.png)
エラーが出たとき
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2716347/1a92592d-71b0-2a25-f304-7affa3e75e3b.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2716347/5e361733-804e-f233-ba34-132f19699b3e.png)

### GUIの作成
とりあえず構成だけ
```
root
└ frame
  ├ entry
  └ button
```