Pythonにおけるデバッガ: pdb, ipdb

この記事は[Tech Train](https://qiita.com/organizations/techtrain)の主催する[【User Ver.】TechTrain Advent Calendar 2019](https://qiita.com/advent-calendar/2019/techtrain)の25日目、最後の記事です！

大阪工業大学の[小林](https://bit.ly/2A58523)が担当します。


Pythonでのデバッガの記事です。
プログラミングを始めた当初は、デバッグといえばprintデバッグでしたが、デバッグツールの使い方を覚えればより快適なデバッグができるようになりました。

紹介するのはpdb、ipdbです。
ipdbは絶望的なまでに日本語資料がありません。
というか英語資料もなかなかありません。
頑張って調べてまとめました。

#pdb
##pdbとは？
pdbはデバッガの1つです。
デバッガとはエラーの原因を突き止めて修正するというデバッグをスムーズに簡単に行うことができるようにするツールです。
ドキュメントは[こちら](https://docs.python.org/ja/3/library/pdb.html)

##デバッグモードへの入り方
以下のようなコードがあるとします。

```python:main.py
import json
import requests

connpass_url = "https://connpass.com/api/v1/event/"
connpass_params = {
    "order": 3,
    "count": 100,
}

response = requests.get(connpass_url, connpass_params)
data = response.json()

osaka_events = []
for event in data["events"]:
    if event["address"] is not None:
        if "大阪" in event["address"]:
            osaka_events.append(event)
```

[Connpass API](https://connpass.com/about/api/)から大阪で開かれるイベントのみを抜き出して配列`osaka_events`に格納するプログラムです。
このまま実行しても次のように何も表示されません。
`print()`も使っていないので。

```
asuha on asuha-HP-EliteDesk-800-G4-TWR in ~/ipdb-qiita
$ python main.py 
asuha on asuha-HP-EliteDesk-800-G4-TWR in ~/ipdb-qiita
$ 
```


`osaka_events`にデータを格納できたかを逐次確認したい場合、次のように書きます。

```python:main.py
import json
import requests

connpass_url = "https://connpass.com/api/v1/event/"
connpass_params = {
    "order": 3,
    "count": 100,
}

response = requests.get(connpass_url, connpass_params)
data = response.json()

osaka_events = []
import pdb; pdb.set_trace() # 追加
for event in data["events"]:
    if event["address"] is not None:
        if "大阪" in event["address"]:
            osaka_events.append(event)
```

これを実行すると…

```
asuha on asuha-HP-EliteDesk-800-G4-TWR in ~/ipdb-qiita
$ python main.py 
> /home/asuha/ipdb-qiita/main.py(15)<module>()
-> for event in data["events"]:
(Pdb) 
```

pdbの画面に入ることができました。
`(Pdb)`と書かれたところにコマンドを打っていくことができます。

##pdbの関数
pdbに入ることができる関数は`pdb.set_trace()`だけではありませんが、自分は使ったことがない。

これらは先程と同じように**プログラムの中**に書くものです。

自分もよくわかっていないので[ドキュメント](https://docs.python.org/ja/3/library/pdb.html)参照

##pdb基本コマンド
デバッグモードに入ったあとに打つことができるコマンドです。

詳細は[ドキュメント](https://docs.python.org/ja/3/library/pdb.html)へ

コマンドの一覧はデバッグモードで`help`もしくは短縮形の`h`を打つことで見ることができます。

```
asuha on asuha-HP-EliteDesk-800-G4-TWR in ~/ipdb-qiita
$ python main.py 
> /home/asuha/ipdb-qiita/main.py(15)<module>()
-> for event in data["events"]:
(Pdb) h

Documented commands (type help <topic>):
========================================
EOF    c          d        h         list      q        rv       undisplay
a      cl         debug    help      ll        quit     s        unt      
alias  clear      disable  ignore    longlist  r        source   until    
args   commands   display  interact  n         restart  step     up       
b      condition  down     j         next      return   tbreak   w        
break  cont       enable   jump      p         retval   u        whatis   
bt     continue   exit     l         pp        run      unalias  where    

Miscellaneous help topics:
==========================
exec  pdb

(Pdb) 
```

また、`h <コマンド名>`と打つと、そのコマンドの説明を見ることができます。
例えば、`h alias`と打ってみましょう。

```
(Pdb) h alias
alias [name [command [parameter parameter ...] ]]
        Create an alias called 'name' that executes 'command'.  The
        command must *not* be enclosed in quotes.  Replaceable
        parameters can be indicated by %1, %2, and so on, while %* is
        replaced by all the parameters.  If no command is given, the
        current alias for name is shown. If no name is given, all
        aliases are listed.

        Aliases may be nested and can contain anything that can be
        legally typed at the pdb prompt.  Note!  You *can* override
        internal pdb commands with aliases!  Those internal commands
        are then hidden until the alias is removed.  Aliasing is
        recursively applied to the first word of the command line; all
        other words in the line are left alone.

        As an example, here are two useful aliases (especially when
        placed in the .pdbrc file):

        # Print instance variables (usage "pi classInst")
        alias pi for k in %1.__dict__.keys(): print("%1.",k,"=",%1.__dict__[k])
        # Print instance variables in self
        alias ps pi self
(Pdb) 
```
`alias`についての細かな解説が得られます。(英語ですが)

これは[ドキュメント](https://docs.python.org/ja/3/library/pdb.html)に書いてあることと同じなので英語を読むのが嫌なときはドキュメントを読みましょう。

いくつか基本のコマンドを紹介します。
ここに載せるもの以外も色々あるのでチェックしてみてください。

###p(print), pp(prety-print)
変数の値を出力します。
Pythonでいうprint()

上の`p`では見づらいことがあります。
例えば次のような場合。

以下を実行すると、

```
(Pdb) p data["events"]
```
![Screenshot from 2019-12-24 18-37-59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/28a485f6-6ff4-096e-83e7-fe29b787bfa1.png)

改行なしだと見にくいですね。

`pp`を使うと、Pythonでpprintを使ったときのような効果が得られます。

```
(Pdb) pp data["events"]
```
![Screenshot from 2019-12-24 18-39-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/8956bb02-3da9-b439-7574-92c46ccd008f.png)

###l(list)
現在の行と前後5行ずつを出力します。

```
(Pdb) l
 10     response = requests.get(connpass_url, connpass_params)
 11     data = response.json()
 12  
 13     osaka_events = []
 14     import pdb; pdb.set_trace()
 15  -> for event in data["events"]:
 16         if event["address"] is not None:
 17             if "大阪" in event["address"]:
 18                 osaka_events.append(event)
[EOF]
(Pdb) 
```

この場合後ろの行が5行無かったので`[EOF]`が出力されていますね。

###ll(long-list)
現在の関数またはフレームの全ソースコードを表示します。
この場合は全てのコードを表示しています。

```
(Pdb) ll
  1     import json
  2     import requests
  3  
  4     connpass_url = "https://connpass.com/api/v1/event/"
  5     connpass_params = {
  6         "order": 3,
  7         "count": 100,
  8     }
  9  
 10     response = requests.get(connpass_url, connpass_params)
 11     data = response.json()
 12  
 13     osaka_events = []
 14     import pdb; pdb.set_trace()
 15  -> for event in data["events"]:
 16         if event["address"] is not None:
 17             if "大阪" in event["address"]:
 18                 osaka_events.append(event)
(Pdb) 
```

関数の中に`pdb.set_trace()`を置いた場合、関数の中を全て表示します。

```python
def func():
    import pdb; pdb.set_trace()

func()
```

```
(Pdb) ll
  1     def func():
  2  ->     import pdb; pdb.set_trace()
(Pdb) 
```

###n(next)
現在の行を実行して次の行に移ります。

```
> /home/asuha/ipdb-qiita/main.py(15)<module>()
-> for event in data["events"]:
(Pdb) n
> /home/asuha/ipdb-qiita/main.py(16)<module>()
-> if event["address"] is not None:
(Pdb) n
> /home/asuha/ipdb-qiita/main.py(17)<module>()
-> if "大阪" in event["address"]:
(Pdb) n
> /home/asuha/ipdb-qiita/main.py(15)<module>()
-> for event in data["events"]:
(Pdb) n
> /home/asuha/ipdb-qiita/main.py(16)<module>()
-> if event["address"] is not None:
(Pdb) n
> /home/asuha/ipdb-qiita/main.py(15)<module>()
-> for event in data["events"]:
```

15行目→16行目→17行目と写っていき、`for`文の終わりまでくるとまた15行目に戻って変数`event`に新しい値を入れてプログラムを続行しています。

関数を呼び出す行に当たったときはその関数の中は1行1行実行せずに関数1行だけ実行して次の行へ進みます。
関数の中も1行1行実行したいときは`s(step)`を使いましょう。

###r(return)
現在の関数が終わるまで実行する。

###a(args)
現在の関数の引数リストを出力する。

###w(where)
現在の行を表示します。
デバッグを初めてすぐや`n`を押したときなどに表示されるものそのままです。
`p`などを使って色々表示することでログが流れてしまい、今の行が思い出せないときに使う。

```
(Pdb) osaka_events
[]
(Pdb) w
> /home/asuha/ipdb-qiita/main.py(17)<module>()
-> if "大阪" in event["address"]:
(Pdb) 
```

###q(quit)
デバッグを辞める際に使います。

###c(continue)
次のブレークポイントが来るまで実行する。
ブレークポイントを置いていなかったら最後まで一気に実行してデバッガを抜けてしまうので注意。

###b(breakpoint)
brakepointじゃないよ。
`b <行数>`でブレークポイントを置きます。

```
(Pdb) b 17
Breakpoint 1 at /home/asuha/ipdb-qiita/main.py:17
(Pdb) b 18
Breakpoint 2 at /home/asuha/ipdb-qiita/main.py:18
(Pdb) 
```

このとき、`Breakpoint 1`という風に番号が与えられています。

引数無しでブレークポイントの一覧を表示します。

```
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:17
2   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:18
(Pdb) 
```

###cl(clear)
引数無しで実行すると全てのブレークポイントを消します。
`Clear all breaks?`と聞かれるので`y(yes)`を入力します。

```
(Pdb) b 17
Breakpoint 1 at /home/asuha/ipdb-qiita/main.py:17
(Pdb) b 18
Breakpoint 2 at /home/asuha/ipdb-qiita/main.py:18
(Pdb) cl
Clear all breaks? y
Deleted breakpoint 1 at /home/asuha/ipdb-qiita/main.py:17
Deleted breakpoint 2 at /home/asuha/ipdb-qiita/main.py:18
(Pdb) 
```

ブレークポイントの番号を引数に指定することで1つだけ選択して消すことができます。

```
(Pdb) b 17
Breakpoint 3 at /home/asuha/ipdb-qiita/main.py:17
(Pdb) b 18
Breakpoint 4 at /home/asuha/ipdb-qiita/main.py:18
(Pdb) b
Num Type         Disp Enb   Where
3   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:17
4   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:18
(Pdb) cl 3
Deleted breakpoint 3 at /home/asuha/ipdb-qiita/main.py:17
(Pdb) b
Num Type         Disp Enb   Where
4   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:18
(Pdb) 
```

###disable, enable
指定したブレークポイントを無効にしたり有効にしたりできます。

```
(Pdb) b
Num Type         Disp Enb   Where
4   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:18
(Pdb) disable 4
Disabled breakpoint 4 at /home/asuha/ipdb-qiita/main.py:18
(Pdb) b
Num Type         Disp Enb   Where
4   breakpoint   keep no    at /home/asuha/ipdb-qiita/main.py:18
(Pdb) enable 4
Enabled breakpoint 4 at /home/asuha/ipdb-qiita/main.py:18
(Pdb) b
Num Type         Disp Enb   Where
4   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:18
(Pdb) 
```

`Enb`のところが`no`になったり`yes`になったりしているのがわかります。

引数無しで実行したからと言って全部変更されるわけではないらしい。

```
(Pdb) b
Num Type         Disp Enb   Where
4   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:18
5   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:17
(Pdb) disable
(Pdb) b
Num Type         Disp Enb   Where
4   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:18
5   breakpoint   keep yes   at /home/asuha/ipdb-qiita/main.py:17
(Pdb) 
```

###alias
自分オリジナルのコマンドを作ることができる。
あまり使いみちが思い浮かばないかもしれませんが、この`alias`は次に紹介する`pdbrc`を使うことで真価を発揮します。

```
(Pdb) alias count len(osaka_events)
(Pdb) count
0
(Pdb) 
```

##.pdbrc
`rc`は`Run Commands`の略です。
デバッガが起動したときにここに書いてあるコマンドを一通り実行します。
これは特に`alias`を設定するときに役立ちます。
pdbに自分好みのコマンドを自由に作ることができるのです。

この`.pdbrc`はホームディレクトリとカレントディレクトリにあるものを順番に読み込んで実行します。
ホームディレクトリにあるコマンドとカレントディレクトリにあるコマンドの名前が被った場合はカレントディレクトリのもので上書きされます。

[ドキュメント](https://docs.python.org/ja/3/library/pdb.html)を参考に次のように書きました。

```
alias pi for k in %1.keys(): print("%1.",k,"=",%1[k])
```

辞書型のデータの値を一つずつ出力するコマンドです。
このように引数を取ることもできるため、応用幅は広いと言えます。
以下実行結果

```
(Pdb) pi osaka_events[0]
osaka_events[0]. event_id = 160650
osaka_events[0]. title = 1/9(木)Umekitaforce新年LT大会
osaka_events[0]. catch = 
osaka_events[0]. description = <p>第26回UmekitaForceのご案内です。</p>
<p>今回は、新年一発目ということで、+LT大会を開催したいと思います。
Winter20についてや、Dreamforceについてのお話などざっくばらんにお話聞けると思います。</p>
<h2>アジェンダ</h2>
<ul>
<li>本日の会の説明：10分</li>
<li>LT
　5人くらい？
-DF19お土産プレゼント</li>
<li>まとめ、質問会：15分</li>
<li>懇親会 (近隣のお店にて)</li>
</ul>
<h2>事前に準備するもの</h2>
<ul>
<li>やる気！ 元気！</li>
<li>※Wifiはご用意しますが、接続数が多くなると不安定になる可能性もございますのでお持ちの方はご持参いただけると助かります。</li>
</ul>
osaka_events[0]. event_url = https://umekitaforce.connpass.com/event/160650/
osaka_events[0]. started_at = 2020-01-09T19:30:00+09:00
osaka_events[0]. ended_at = 2020-01-09T21:30:00+09:00
osaka_events[0]. limit = None
osaka_events[0]. hash_tag = 
osaka_events[0]. event_type = participation
osaka_events[0]. accepted = 2
osaka_events[0]. waiting = 0
osaka_events[0]. updated_at = 2019-12-24T19:01:50+09:00
osaka_events[0]. owner_id = 144430
osaka_events[0]. owner_nickname = HidekiAkizuki
osaka_events[0]. owner_display_name = hideki_akizuki
osaka_events[0]. place = 株式会社タンバリン
osaka_events[0]. address = 大阪市北区同心1丁目11番6号
osaka_events[0]. lat = 34.701625200000
osaka_events[0]. lon = 135.515051900000
osaka_events[0]. series = {'id': 3789, 'title': 'Umekita Force', 'url': 'https://umekitaforce.connpass.com/'}
(Pdb) 
```

他の人の`.pdbrc`も参考にしてみると良いでしょう。
[Python debugging tips - Stack Overflow](http://stackoverflow.com/questions/1623039/python-debugging-tips)


#ipdb
##ipdbとは？
[ipdbのgithubのページ](https://github.com/gotcha/ipdb)にはこうあります。

>ipdb exports functions to access the IPython debugger, which features tab completion, syntax highlighting, better tracebacks, better introspection with the same interface as the pdb module.

>ipdbはIPythonデバッガにアクセスするための関数を提供します。IPythonデバッガはタブ補完、syntax highliting、よりよいtraceback、よりよいイントロスペクションなどの機能とpdbと同じインターフェースを持ちます

**ipdbはpdbの拡張であるため、pdbの機能はそのまま使える**と思ってよいでしょう。

##pdbとipdbの違い
ipdbがpdbと違うところを並べていきます。
以下はすべてipdbの特徴です。
###インストールが必要
pdbはPythonにデフォルトで入っていますが、ipdbを使うには下記のようなコマンドでインストールが必要です。

```
pip install ipdb
```
###デバッガのデザインが少し違う
これがpdb
![Screenshot from 2019-12-24 17-46-56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/19c13809-ed43-54ee-90e2-3a39c3694618.png)

これがipdb
![Screenshot from 2019-12-24 17-47-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/ae52c413-c4f4-5b82-f7a1-8b18cb592222.png)

ipdbでは色分けがされます。
文法の認識はガバガバですが…
単語の区切りは認識できているので少しは見やすくなっているかも？

さらに現在の行の前後1列を表示しています。
これは現在の行がどこか認識するのに役に立ちそうです。


###タブ補完機能がある
このプログラムでは`osaka_events`という配列を用意しています。
これをosakaとだけ打ったところで、
![Screenshot from 2019-12-24 18-02-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/66d23dcc-fdf8-a9d2-3214-c2b2b50455d3.png)
`Tab`キーを打つと、
![Screenshot from 2019-12-24 18-02-40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/c14fd863-bfd4-a2fe-bc26-c133d4c9588f.png)
上のように補完してくれます。

複数候補がある場合は次のようになります。
このときは`o`だけ入力して`Tab`キーを押しました。
![simplescreenrecorder-2019-12-24_18.09.54.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/8c7a22e1-0b5c-9893-d8c5-afe7fbbc4d0d.gif)

###使えるコマンドが増える
pdbのコマンド
![Screenshot from 2019-12-24 21-20-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/4c86a93e-3ca2-01b9-8f23-5c9619e9475a.png)
ipdbのコマンド
![Screenshot from 2019-12-24 21-20-25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/1c5bf9fd-0bbc-46de-858d-14b776390bd8.png)

以下がipdbで追加されたコマンドです。
これらはIPythonのマジックコマンドをipdb向けに使えるようにしたコマンドです。

- pdef
- pdoc
- pfile
- pinfo
- pinfo2
- psource

####pdef

```
ipdb> h pdef                                        
Print the call signature for any callable object.

        The debugger interface to %pdef
```

指定したオブジェクトの引数の情報を表示する。

![Screenshot from 2019-12-25 01-28-45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/7e0bd856-5680-4dee-883f-afed41bae181.png)


####pdoc

```
ipdb> h pdoc                                        
Print the docstring for an object.

        The debugger interface to %pdoc.
```

docstringを表示します。

```
ipdb> pdoc requests
```
![Screenshot from 2019-12-25 00-49-50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/f3d1dd0a-8ce5-e25a-3566-d4540e8995a9.png)




####pfile

```
ipdb> h pfile                                       
Print (or run through pager) the file where an object is defined.

        The debugger interface to %pfile.
```

指定したオブジェクトが定義されているファイルを開いて表示します。

![Screenshot from 2019-12-25 01-37-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/c7a18621-85e6-3410-5764-8442a72f9d55.png)


####pinfo

```
ipdb> h pinfo                                       
Provide detailed information about an object.

        The debugger interface to %pinfo, i.e., obj?.
```

引数に指定したオブジェクトの情報を表示します。

![Screenshot from 2019-12-25 01-00-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/2d994872-b23a-56b7-1ab9-d4cea6d74ac0.png)


####pinfo2

```
ipdb> h pinfo2                                      
Provide extra detailed information about an object.

        The debugger interface to %pinfo2, i.e., obj??.
```

引数に指定したオブジェクトの情報を`pinfo`より詳細に表示します。

![Screenshot from 2019-12-25 01-00-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/7e9a495e-5617-e5ae-6c1e-63aea6d8c33b.png)


####psource

```
ipdb> h psource                                     
Print (or run through pager) the source code for an object.
```

引数に指定したオブジェクトのソースコードを表示します。

![Screenshot from 2019-12-25 01-15-57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397212/0ea6928c-6bc5-96c4-d8d1-3b8b56a65c09.png)



##ipdbの導入方法
pdbと変わらず、

```python
import ipdb; ipdb.set_trace()
```

といったように書けば動きます。
ただ、上でも述べたように、インストールが必要である点には注意しましょう。

#参考
[pdb --- Python デバッガ &#8212; Python 3.8.1 ドキュメント](https://docs.python.org/ja/3/library/pdb.html)
[gotcha/ipdb: Integration of IPython pdb](https://github.com/gotcha/ipdb)
[cpython/pdb.py at master · python/cpython](https://github.com/python/cpython/blob/master/Lib/pdb.py)
[Python debugging tips - Stack Overflow](http://stackoverflow.com/questions/1623039/python-debugging-tips)
[Christian Long's Blog &middot; ipdb, the iPython&nbsp;debugger](http://www.christianlong.com/blog/ipdb-the-ipython-debugger.html)
[Built-in magic commands — IPython 7.10.2 documentation](https://ipython.readthedocs.io/en/stable/interactive/magics.html)