🐞

Pythonデバッガpdbを使ってみましょう

2023/05/28に公開

はじめに

Pythonは独学で学びました。キョです。

みなさんはPythonプログラムをデバッグする時、どうやっていますか?
私はprintをゴリゴリ使っていますねw
簡単なプログラムとか、自分で作ったプログラムの場合はいいかもしれないですが、
ちょっと複雑なプログラムになるとやはりデバッガを使いたくなりますね。
それで、Python公式のデバッガpdbをちょっと触ってみましたので、
簡単に紹介したいと思います。

pdbとは?

pdbはPython公式のコマンドラインデバッガになります。
公式説明は以下になります。

モジュール pdb は Python プログラム用の対話型ソースコードデバッガを定義します。 (条件付き)ブレークポイントの設定やソース行レベルでのシングルステップ実行、スタックフレームのインスペクション、ソースコードリスティングおよびあらゆるスタックフレームのコンテキストにおける任意の Python コードの評価をサポートしています。事後解析デバッギングもサポートし、プログラムの制御下で呼び出すことができます。

pdbの使い方

まずは、中断してデバッガを起動する方法です。
公式ドキュメントには二つの方法が記載されていますが、
個人的には、デバッグしたい箇所にbreakpointを追加する方法が好きです。

breakpoint()

https://docs.python.org/ja/3/library/pdb.html

それでは、以下のサンプルコードで、pdbの基本的な使い方法を紹介したいと思います。

このpythonプログラムを実行すると以下のようになります。

$ python example.py
> /home/user/python/example.py(7)main()
-> for i in range(3):
(Pdb)

これで、pdbが実行されましたね。
出力している内容を見てみましょう。

まずは一行目の内容は以下になります。

> /home/user/python/example.py(7)main()
  • /home/user/python/example.py:実行中のファイル名
  • (7):今ファイルの何行目で止まっているか
  • main():今はどの関数中か

そして、その次の行

-> for i in range(3):

これは次実行する行の内容です。

実際のコードの内容を確認しながら見ると以下のような動作になります。

  • pyファイルの6行目にbreakpointを追加している場合は、7行目の処理を実行せず、7行目で止まっている
  • 次実行する処理が7行目の処理になる

これでプログラムの処理が止まってデバッグできるようになります。
それでは、デバッグする時よく使うコマンドを一通り見てみましょう。

pコマンド

変数の値を確認するコマンドになります。
pコマンドの後ろに値を出力したい変数を指定することで、その変数の値が出力されます。
試しに、list変数の値を確認してみましょう。

$ python example.py
> /home/user/python/example.py(7)main()
-> for i in range(3):
(Pdb)p list
[]
(Pdb)

まだ何もいれてないので、初期値のままですね。
次は、まだ定義されていない変数iを出力してみましょう

・・・
(Pdb) p i
*** NameError: name 'i' is not defined
(Pdb)

iはまだ定義されていないと怒られましたね。

lコマンド

現在のファイルのソースコードを表示します。
引数を指定しないと、現在の行の前後 11 行分を表示するか、直前の表示を続行します。
引数に . を指定すると、現在の行の前後 11 行分を表示します。

試してみましょう。
まずは一度lを入力してみます。

・・・
(Pdb) l
  2         return x * x
  3
  4     def main():
  5         list = []
  6         breakpoint() # ここでデバッグを開始
  7  ->     for i in range(3):
  8             s = square(i)
  9             list.append(s)
 10
 11         return list
 12
(Pdb)

現在の行の前後11行が表示されましたね。
この状態でもう一度lを入力してみましょう。

(Pdb) l
 13     if __name__ == "__main__":
 14         result = main()
 15         print(result)
[EOF]
(Pdb)

一回目のlコマンド入力時で表示した内容の続きの部分が表示されました。
ここで「l .」を入力すると一回目のlコマンド入力時と同じ内容が出力されることも確認できますね。

(Pdb) l .
  2         return x * x
  3
  4     def main():
  5         list = []
  6         breakpoint() # ここでデバッグを開始
  7  ->     for i in range(3):
  8             s = square(i)
  9             list.append(s)
 10
 11         return list
 12
(Pdb)

llコマンド

現在の関数またはフレームの全ソースコードを表示します。

(Pdb) ll
  4     def main():
  5         list = []
  6         breakpoint() # ここでデバッグを開始
  7  ->     for i in range(3):
  8             s = square(i)
  9             list.append(s)
 10
 11         return list
(Pdb)

nコマンド

現在行を実行して、次の行で止まります。
現在行が関数の呼び出しになっている場合は、その関数が返るまで実行します。
試してみましょう。

(Pdb) n
> /home/user/python/example.py(8)main()
-> s = square(i)
(Pdb) p i
0
(Pdb)

7行目が実行されて、8行目で止まっていました。
7行目が実行されるとi変数が定義されますので、iも確認できましたね。
この状態でもう一度nコマンドを実行してみましょう。

(Pdb) n
> /home/user/python/example.py(8)main()
-> s = square(i)
(Pdb) p i
0
(Pdb) n
> /home/user/python/example.py(9)main()
-> list.append(s)
(Pdb) p s
0
(Pdb)

square関数が返るまで実行されたことも確認できました。

sコマンド

現在の行を実行し、最初に実行可能なものがあらわれたときに (呼び出された関数の中か、現在の関数の次の行で) 停止します。
sコマンドはnコマンドと違って、現在の行が関数の呼び出しになる場合は、
呼び出された関数の中の最初行で止まります。
実際に試してみましょう。

> /home/user/python/example.py(8)main()
-> s = square(i)
(Pdb) s
--Call--
> /home/user/python/example.py(1)square()
-> def square(x):
(Pdb)

square関数を呼び出す行でsコマンドを実行すると、square関数に入ることが確認できました。
これで、呼び出す関数の中で何か発生したかも確認できます。

rコマンド

現在の関数が返るまで実行を継続します。

(Pdb) s
--Call--
> /home/user/python/example.py(1)square()
-> def square(x):
(Pdb) r
--Return--
> /home/user/python/example.py(2)square()->1
-> return x * x
(Pdb)

retvalコマンド

現在の関数が返るまで実行した場合、戻り値の確認ができます。
例えば、

  • rコマンドを実行した後、retvaleコマンドで戻り値を確認
  • nコマンド(あるいはrコマンド)を利用して、関数が返るまで実行した後、retvaleコマンドで戻り値を確認

の場合ですね。

(Pdb) r
--Return--
> /home/user/python/example.py(2)square()->1
-> return x * x
(Pdb) retval
1
(Pdb)

untコマンド

引数なしだと、現在の行から 1 行先まで実行します。
lineno を指定すると、番号が lineno 以上である行に到達するまで実行します。
untコマンドはループから出たい場合よく使えます。
今回のサンプルを例にした場合、
ループは7~9行までなので、ループから出たい場合はlinenoに10を指定する必要があります。

> /home/user/python/example.py(7)main()
-> for i in range(3):
(Pdb) unt 10
> /home/user/python/example.py(11)main()
-> return list

cコマンド

次のブレークポイントまで実行します。
サンプルコードの場合は、プログラムの最後まで実行します。

$ python example.py
> /home/user/python/example.py(7)main()
-> for i in range(3):
(Pdb) c
[0, 1, 4]

bコマンド

linenoを指定して、指定行にブレークポイントを設定します。
例えば、毎回のループを確認したい場合、ループの中にブレークポイントを設定すればいいです。
今回は9行目にブレークポイントを設定して確認します。

$ python example.py
> /home/user/python/example.py(7)main()
-> for i in range(3):
(Pdb) b 9
Breakpoint 1 at /home/user/python/example.py:9
(Pdb) c
> /home/user/python/example.py(9)main()
-> list.append(s)
(Pdb) c
> /home/user/python/example.py(9)main()
-> list.append(s)
(Pdb) c
> /home/user/python/example.py(9)main()
-> list.append(s)
(Pdb)

コードを修正しなくて、pdbを起動する方法

サンプルコードの場合は、直接コードを修正して、ブレークポイントを入れましたが、
コードを修正しなくて、pdbを起動する方法もあります。
「python -m pdb」でプログラムを起動するとpdbが起動されます。

$ python -m pdb example.py
> /home/user/python/example.py(1)<module>()
-> def square(x):
(Pdb)

うまく起動できましたね。
これからは上で紹介したコマンドを利用して、
デバッグを行えばいいです。

終わり

pdbの使い方とよく使うコマンドを紹介しました。
ぜひ、次デバッグしたい時、使ってみてください。
使ってみて、そのうち他のコマンドも確認したくなりますでしょうw

それでは、よきデバッグライフをお送りください。

Discussion