jupyterでのエラーデバッグ(%debug)のススメ

## 0 はじめに

Jupyterばかり使用しているくせにお恥ずかしながらデバッグコマンドを知らなかったので、今回は%debugというマジックコマンドについて学んだ時のメモです。

今回も誰にでもわかるような簡単な例で図も多用しつつ説明します。

## 1 まず%debugって何？
pythonに備わっている対話型デバッガーのpdb機能の１つでマジックコマンド形式のものである。
実例でどんなことができるか見てもらえればわかると思います。

## 2 実例

エラー時にデバッグできるとなれば、身近で簡単なエラー・・
除算にて0で割るパターンを考えてみます。

### 2-1 エラーを出してみる

違いを実感する為に、エラーになる関数の中に無茶苦茶重い処理があったと仮定する。
※今回は20秒の待機時間がその重い処理の代わりです

```Python:
import time

def f1(x, y):
    dd = 1 #何も関係ないけど説明で入れた
    return x / y

def f2(x):
    time.sleep(20) #何か無茶苦茶重い処理を実行(20秒)
    a = x
    b = a+1 #b=2
    c = b-1 #c=1
    d = c-1 #d=0
    return f1(b, d) #関数f1呼び出し

x=1
f2(x)
```

すると以下のようなエラー文が出てくる

![スクリーンショット 2022-04-17 132003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/f80f46c3-ad20-e417-fe94-39d0b621765c.png)



### 2-2 print文でデバッグだと・・・

以下のように調べたい変数の前にprintを入れたりして調査したりしていたと思う。
**別にこれでもいい**んだが、今回のように途中に重い処理があるとそれも毎回実行されるので時間がかかってしまう。

```Python:
import time

def f1(x, y):
    dd = 1
    return x / y

def f2(x):
    time.sleep(20) #何か無茶苦茶重い処理を実行(20秒)
    a = 1
    b = a+1 #b=2
    print(f"b={b}") #デバッグ用print
    c = b-1 #c=1
    print(f"c={c}") #デバッグ用print
    d = c-1 #d=0
    print(f"d={d}") #デバッグ用print
    return f1(b, d)

x=1
f2(x)
```
```Python:
b=2
c=1
d=0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-1-cb4375aaee57> in <module>
・・以下(略)・・・
```

### 2-3 %debugでデバッグ実行
こういう時にマジックコマンドである`%debug`を使うと便利である。※新規のセルで実行
今回のように何かエラーが出た際にその状況を「対話的に」検査することが可能。
※**ただし、最後に発生したTracebackエラーに対してのみ有効なので、エラー後すぐに呼び出す必要があることに注意**


なお、同じエラーデバッグにも色々やり方はある(`%pdb onを先頭に付与する`とか)ようですが、今回の例だと途中に時間がかかる処理があるため圧倒的に`%debug`の方がいいです。

とりあえず、先程のエラー発生後に以下のようにコマンドを打つ。
```Python:
 %debug
```

すると以下のような表示が出る
![スクリーンショット 2022-04-17 130558.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/5950a87a-6e05-0f21-4352-6c705e4fe52a.png)

最後にエラーが出たのが関数f1の中のreturn文の個所なので、そのエラー発生時にどんなことが起きているのか？が知りたければ、例えば以下のように記述すれば色々その時の状態をつかめる

![スクリーンショット 2022-04-17 131246.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/16a4f57c-edbb-fd69-2a8f-eb08c79079af.png)

そして、f1を呼び出している関数f2を確認したければ、入力に「u」「up」と打ち込んでエンターを押せば上位関数へ移動できる。
 ※下図で関数がf2に変化していることがわかる

![スクリーンショット 2022-04-17 131642.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/65f238d9-2a4d-df5c-a58b-6c6b778a375a.png)

後は同じ要領で関数f2の中の状態を確認すればいい。
**デバッグを終えたら「q」でデバッガをOFFすることを忘れずに**

一応まとめると以下のようなイメージである。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/8211af49-0e55-2ef3-20a9-ecde5af5b9b1.png" width=80%>


なお、デバッガにはショートカットコマンドがあり、**変数dを調べるのにただ「d」と打ち込むとdownの意味で解釈されるので注意。**
`注意すべき(活用すべき)1英字コマンドは以下。`


|コマンド(短縮コマンド)  | 内容 |
|---|---|
|args(a)|今いる階層の関数の引数をprint表示|
|break(b) |ブレークポイント設置|
|continue(c) |次のブレークポイントまで実行|
|down(d) |1階層下の関数(フレーム)へ移る|
|up(u) |1階層上の関数(フレーム)へ移る|
|list(l) |周辺のソースコードを表示 ※エラーで表示されてる関数範囲は少ない為、もっと表示させたい場合に使用|
|help(h) |ここに書かれているようなコマンドを出力させるhelp|
|next(n) |次の行を実行する※関数の場合入らずに|
|step(s) |次の行を実行する※関数の場合中に入る|
|return(r) |関数の最後まで実行|
|quit(q)|対話デバッグを終了する|

### 2-4 関数の途中でデバッグ「import pdb;pdb.set_trace()」

関数の中で動的に変数が変化する場合はこれが便利。(エラー時以外でも普通に使用可能)
ただし関数は実行されるので、重い処理の時はその時間もかかってしまうが・・


```Python:
import time

def f1(x, y):
    dd = 1
    return x / y

def f2(x):
    time.sleep(20) #何か無茶苦茶重い処理を実行(20秒)
    a = 1
    b = a+1 #b=2
    import pdb;pdb.set_trace() #ここでブレークして止める指示①
    c = b-1 #c=1
    import pdb;pdb.set_trace() #ここでブレークして止める指示②
    d = c-1 #d=0
    return f1(b, d)

x=1
f2(x)
```

すると以下のように指定したブレーク(14行目)で止まり、その時まだ変数cは空っぽなので怒られているのもわかる。
また、先程説明したようなコマンドを使って次のブレークポイントまで移動とかを行うことが可能。
(例では「s」で15行目に移動させてprint(c)を成功させている)


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/e122cc17-f6b0-082f-80a3-9a8035761304.png" width=70%>

## 3 おわりに

意外とこういうコマンドって、絶対必要なわけじゃない為に意図して学ぼうとしないとなかなか身につかないまま時がたってしまうものなんだなぁ・・なんてしみじみ感じながら記事を書いてきましたが、いかがだったでしょうか？
printでもなんとかなるとは思いますが、複雑な関数になればなるほど調べるのもだし、**後でprintを消すのも意外と面倒なんですよね・・**

それでは今回はここまで。

## 参考リンク集
https://ipython.readthedocs.io/en/stable/interactive/magics.html

https://chrieke.medium.com/jupyter-tips-and-tricks-994fdddb2057

https://www.youtube.com/watch?v=b8g-8T0amuk