Pythonのデバッグを完全理解
デバッグ
デバッグは開発者にとって、とても重要なスキルの1つです。デバッグをすることで、エラーを正確に特定し、プログラムのバグを見つけることができます。Pythonでは、さまざまなデバッグツールやパッケージ(デバッガーとも言う)が提供されています。これらをどう使うかを紹介していきます。
pdbでデバッグ
「pdb」はPython標準ライブラーのデバッグツールで、Pythonプログラムにインタラクティブなソースコードデバッグ機能を提供しています。使い方はC言語の「gdb」と類似しています。pdb
の主な機能として、「ブレークポイント」の設置、「ステップ実行」、「スタックフレーム」のチェック、変数の値を動的に変更するなどあります。pdb
はよく使われるデバッグ操作コマンドを提供しています。
コマンド | 短縮コマンド | 説明 |
---|---|---|
break | b | ブレークポイントの設置;b <行数> で指定行数にブレークポイントを設置できる |
continue | cont/c | 次のブレークポイントまで実行再開 |
next | n | 次の行を実行する;次の行はサブプログラム(関数など)の場合、中に入らない |
step | s | 次の行を実行する;次の行はサブプログラム(関数など)の場合、中に入る |
where | bt/w | 「スタックトレース」を表示 |
enable | - | 無効にされたブレークポイントを有効にする |
disable | - | 有効にされたブレークポイントを無効にする |
pp/p | - |
p <変数名> で変数をプリントする |
list | l | 現在行周辺のソースを表示(llコマンドで、より広範囲のソースを表示できる) |
up | u | 上のスタックフレームへ移動 |
down | d | 下のスタックフレームへ移動 |
restart | run | デバッグをリスタートする |
args | a | 関数の引数をプリントする |
clear | cl | 全てのブレークポイントを削除;cl <番号> で指定番号のブレークポイントを削除できる |
return | r | 関数の終わりのまで実行する |
help | h |
h <コマンド名> でコマンドのhelpを表示する |
quit | q | デバッグを終了する |
2通りの方法で、pdb
デバッガーを起動できます。1つは、コマンドライン引数でpdb
モジュールを指定し、Pythonファイルを起動する方法です。
python -m pdb test_pdp.py
もう1つは、Pythonコードの中で、pdb
モジュールのset_trace
でブレイクポイントを設定する方法です。プログラムは、ブレイクポイントまで実行されたら、自動的に中断し、pdbデバッガーを起動します。
import pdb
def factorial(n, sum=0):
if n == 0:
return sum
pdb.set_trace()
sum += n
print(sum)
return factorial(n-1, sum)
if __name__ == '__main__':
factorial(5)
この2つの方法の間に特に違いはないが、どれを選ぶかはケースバイケースです。コードが短い場合は、コマンドライン引数方式でpdb
を起動しても問題ないでしょう。逆に、大きなプログラムになると、デバッグしたい箇所に事前にset_trace
でブレイクポイントを設置したほうがやりやすいです。
pdb
デバッガーが起動されたら、前述のコマンドでデバッグすることができるようになります。下の例は、末尾再帰の階乗関数をデバッグしたものです。まず、bt
で該当関数の呼び出しスタックをチェックし、list
でPythonのコードを見ました。次に、p
でsum
の値をプリントし、r
で再帰のreturn
を追ってみました。そして最後に、q
でデバッグを終了しました。
kaito@MacBook-Pro debug % python factorial.py
> /Users/kaito/Desktop/debug/factorial.py(9)factorial()
-> sum += n
(Pdb) bt
/Users/kaito/Desktop/debug/factorial.py(15)<module>()
-> factorial(5)
> /Users/kaito/Desktop/debug/factorial.py(9)factorial()
-> sum += n
(Pdb) list
4 def factorial(n, sum=0):
5 if n == 0:
6 return sum
7
8 pdb.set_trace()
9 -> sum += n
10 print(sum)
11 return factorial(n-1, sum)
12
13
14 if __name__ == '__main__':
-> sum += n
(Pdb) p sum
0
-> sum += n
(Pdb) r
5
> /Users/kaito/Desktop/fluent python/debug/factorial.py(9)factorial()
-> sum += n
(Pdb) r
9
> /Users/kaito/Desktop/fluent python/debug/factorial.py(9)factorial()
-> sum += n
(Pdb) r
12
> /Users/kaito/Desktop/fluent python/debug/factorial.py(9)factorial()
-> sum += n
(Pdb) r
14
> /Users/kaito/Desktop/fluent python/debug/factorial.py(9)factorial()
-> sum += n
(Pdb) r
15
--Return--
> /Users/kaito/Desktop/debug/factorial.py(11)factorial()->15
-> return factorial(n-1, sum)
(Pdb) q
Traceback (most recent call last):
...
pdb
の出力は若干地味に感じるなら、出力を綺麗にしてくれるipdb
というパッケージもあります。REPL環境を綺麗にしてくれる、ipython
と同じイメージです。APIは同じですので、import pdb
をimport ipdb as pdb
にすればそのまま使えます。
kaito@MacBook-Pro debug % python factorial.py
> /Users/kaito/Desktop/debug/factorial.py(9)factorial()
8 ipdb.set_trace()
----> 9 sum += n
10 print(sum)
ipdb> r
5
> /Users/kaito/Desktop/debug/factorial.py(9)factorial()
8 ipdb.set_trace()
----> 9 sum += n
10 print(sum)
ipdb> r
9
> /Users/kaito/Desktop/debug/factorial.py(9)factorial()
8 ipdb.set_trace()
----> 9 sum += n
10 print(sum)
ipdb> r
12
> /Users/kaito/Desktop/debug/factorial.py(9)factorial()
8 ipdb.set_trace()
----> 9 sum += n
10 print(sum)
ipdb> r
14
> /Users/kaito/Desktop/debug/factorial.py(9)factorial()
8 ipdb.set_trace()
----> 9 sum += n
10 print(sum)
ipdb> r
15
--Return--
15
> /Users/kaito/Desktop/debug/factorial.py(11)factorial()
10 print(sum)
---> 11 return factorial(n-1, sum)
12
ipdb> q
Exiting Debugger.
本当は色鮮やかでもっと綺麗ですよ。
ipub
以外に、「web-pdb」というブラウザーでデバッグできるパッケージもあります。そして、pdb
とAPIも同じです。イメージとしては以下の図を見れば分かると思います。
また、vimが好きな人はCUIデバッガーの「PuDB」もチェックしてください。イメージとしては以下の図のようになります。CUI好きにはたまらないですね。また、使い方に関しては@Kernel_OGSunさんの記事をお勧めします。
breakpointでデバッグ
Python 3.7からbreakpoint
というビルドイン関数が追加されました(PEP 553)。基本的な使い方は以下のようになります。
def factorial(n, sum=0):
if n == 0:
return sum
breakpoint()
sum += n
print(sum)
return factorial(n-1, sum)
if __name__ == '__main__':
factorial(5)
デフォルトでは、import pdb; pdb.set_trace()
と全く一緒です。
高度な使い方として、PYTHONBREAKPOINT
という環境変数を設定することで、色々な機能を追加できます。例えば、PYTHONBREAKPOINT=0
で、デバッグをオフにすることができ、コードの中のbreakpoint()
を1個1個消す必要がなくなります。
それから、PYTHONBREAKPOINT='importable.callable'
を設定することで、importable.callable
がimport
され、callable
が呼び出されるようになります。例えば、次にように、web-pdb
を使用できます。
PYTHONBREAKPOINT='web_pdb.set_trace' python test_pdb.py
環境変数だけ変えれば、Pythonのコードを1行も変えずに、デバッガーを交換でき、とても便利ですね。
Visual Studio Codeでデバッグ
VSCodeでデバッグするのは非常に簡単です。
まず、左のツールバーから虫アイコンをクリックします。初回の時に、launch.json
を作成する必要がありますが、デフォルトのままでほぼ問題ありません。
続いて、行番号の横をクリックすれば、ブレイクポイントを設置できます。
次に、デバッグ開始をクリックすると、実行されます。
そうすると、次の画面になります。
右上のボタンを利用して、デバッグを操作できます。
Step Over
はpdb
のnext
に相当する機能です。また、Step In
はpdb
のstep
、Step Out
はreturn
になります。GUIは使いやすいですね。
PyCharmでデバッグ
基本、VSCodeと同じようなインタフェースですね。
右上の虫アイコンでデバッグ開始し、正方形アイコンで終了します。
デバッグを操作したい時、主にこちらのボタンが使えます。
左の二子玉を押すと、下の画面に入ります。Condition
でブレイクポイントに入る条件を設定できるのは非常に便利ですね。
一番右の電卓アイコンを押すと、下の画面に入ります。変数などの検証ができます。
まとめ
Pythonのデバッグについて色々見てきました。まずは、標準ライブラリーのpdb
とその派生です。Pythonの開発者として、とりあえずpdb
はおさえましょう。それから、VSCodeとPyCharmのGUIデバッガーも見てみました。基本的に非常に使いやすいものなので、pdb
さえ分かれば、すぐ使えてしまうでしょう。もちろん、高度な使い方は実践で経験を積むしかないですね。
Discussion