🔧

Pythonのデバッグを完全理解

2023/02/13に公開

デバッグ

デバッグは開発者にとって、とても重要なスキルの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のコードを見ました。次に、psumの値をプリントし、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 pdbimport 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.callableimportされ、callableが呼び出されるようになります。例えば、次にように、web-pdbを使用できます。

PYTHONBREAKPOINT='web_pdb.set_trace' python test_pdb.py

環境変数だけ変えれば、Pythonのコードを1行も変えずに、デバッガーを交換でき、とても便利ですね。

Visual Studio Codeでデバッグ

VSCodeでデバッグするのは非常に簡単です。

まず、左のツールバーから虫アイコンをクリックします。初回の時に、launch.jsonを作成する必要がありますが、デフォルトのままでほぼ問題ありません。

続いて、行番号の横をクリックすれば、ブレイクポイントを設置できます。

次に、デバッグ開始をクリックすると、実行されます。

そうすると、次の画面になります。

右上のボタンを利用して、デバッグを操作できます。

Step Overpdbnextに相当する機能です。また、Step InpdbstepStep Outreturnになります。GUIは使いやすいですね。

PyCharmでデバッグ

基本、VSCodeと同じようなインタフェースですね。

右上の虫アイコンでデバッグ開始し、正方形アイコンで終了します。

デバッグを操作したい時、主にこちらのボタンが使えます。

左の二子玉を押すと、下の画面に入ります。Conditionでブレイクポイントに入る条件を設定できるのは非常に便利ですね。

一番右の電卓アイコンを押すと、下の画面に入ります。変数などの検証ができます。

まとめ

Pythonのデバッグについて色々見てきました。まずは、標準ライブラリーのpdbとその派生です。Pythonの開発者として、とりあえずpdbはおさえましょう。それから、VSCodeとPyCharmのGUIデバッガーも見てみました。基本的に非常に使いやすいものなので、pdbさえ分かれば、すぐ使えてしまうでしょう。もちろん、高度な使い方は実践で経験を積むしかないですね。

Discussion