Pythonの標準デバッガであるpdbは、プログラムの実行を一時停止しながら、一行ずつ動作を確認できる強力なツールです。
printデバッグからステップアップして、対話的に原因を切り分けたい初心者の方に向けて、起動方法から基本コマンド、つまずきやすい点の対処までを実例つきで丁寧に解説します。
pdbとは: Pythonの対話的デバッガの基本
pdbはPythonに標準搭載されている対話的デバッガです。
スクリプトの任意の行で実行を止めて、次の行へ一行ずつ進めたり、変数の値を見たり、関数の中へ入って詳細を追うことができます。
外部ツールをインストールする必要がない点も初心者にとって大きな利点です。
一行ずつ実行(ステップ実行)で動きを追う
ステップ実行は、プログラムの流れを丁寧に追いながら「どこで期待と違う挙動が起きているか」を明らかにするのに最適です。
pdbでは、現在の関数の次の行へ進むn
(next)と、関数呼び出しの中へ入るs
(step)を使い分けます。
外側の流れをざっと確認するときはn
、関数内部の処理を詳しく追いたいときはs
が基本です。
変数の中身をその場で確認する
止まっているその場で変数の値を表示できるので、「この時点の値は正しいか」を即座に検証できます。
値を表示するp
と、見やすく整形して表示するpp
を使い分けると読みやすさが向上します。
ブレークポイントで任意の場所で止める
特定の行で止めたいときはブレークポイントを置きます。
pdbではb
(break)で設定しclear
で削除できます。
「特定の条件のときだけ止める」条件付きブレークポイントも使えるため、ループの特定要素や特定の入力に絞ってトラブルを再現しやすくなります。
pdbの始め方と止め方(初心者向け)
ここからは実際に小さなバグを含むサンプルを題材に、pdbの起動と基本操作を体験していきます。
コマンドラインから起動する(python -m pdb)
最小のサンプルとして、合計金額に消費税を掛ける処理にバグがある例を用意します。
期待は「小計×(1+税率)」ですが、誤って「小計×税率」になっているとします。
# buggy_checkout.py
# 税込み価格を計算する簡単なサンプル(意図的にバグあり)
def total_price(items, tax_rate):
"""
items: [(name, price, qty)]
tax_rate: 0.1 のように小数で与える
"""
subtotal = 0
for name, price, qty in items:
subtotal += price * qty # 小計を求める
total = subtotal * tax_rate # バグ: 本来は subtotal * (1 + tax_rate)
return round(total, 2)
if __name__ == "__main__":
items = [("pen", 120, 2), ("notebook", 300, 1)]
print(total_price(items, 0.1))
実行結果(誤った結果が出力されることを確認します)。
54.0
pdbで起動して、冒頭で止めてからブレークポイントを設定してみます。
コマンドラインからpython -m pdb
でスクリプトを渡します。
$ python -m pdb buggy_checkout.py
> .../buggy_checkout.py(1)<module>()
-> def total_price(items, tax_rate):
(Pdb) l
1 -> def total_price(items, tax_rate):
2 """
3 items: [(name, price, qty)]
4 tax_rate: 0.1 のように小数で与える
5 """
6 subtotal = 0
7 for name, price, qty in items:
8 subtotal += price * qty # 小計を求める
9 total = subtotal * tax_rate # バグ: 本来は subtotal * (1 + tax_rate)
10 return round(total, 2)
11
12 if __name__ == "__main__":
13 items = [("pen", 120, 2), ("notebook", 300, 1)]
14 print(total_price(items, 0.1))
(Pdb) b 9 # 9行目で止める
Breakpoint 1 at .../buggy_checkout.py:9
(Pdb) c # ブレークポイントまで実行
> .../buggy_checkout.py(9)total_price()
-> total = subtotal * tax_rate # バグ: 本来は subtotal * (1 + tax_rate)
(Pdb) p subtotal
540
(Pdb) p tax_rate
0.1
(Pdb) n
> .../buggy_checkout.py(10)total_price()
-> return round(total, 2)
(Pdb) p total
54.0
(Pdb) q # デバッガを終了
このように、「止める→値を見る→1行進める」を繰り返すだけで、どの計算が不正かを客観的に確認できます。
コード内で止める(import pdb; pdb.set_trace())
都度コマンドラインからブレークポイントを置く代わりに、コード中で直接デバッガを呼び出すこともできます。
関数の途中に次の1行を挿入すると、その行で必ず停止します。
# buggy_checkout_trace.py
def total_price(items, tax_rate):
subtotal = 0
for name, price, qty in items:
subtotal += price * qty
import pdb; pdb.set_trace() # ここで停止して内部状態を確認できる
total = subtotal * tax_rate # バグがある行の直前
return round(total, 2)
実行すると、挿入した位置でpdbが起動します。
$ python buggy_checkout_trace.py
> .../buggy_checkout_trace.py(7)total_price()
-> total = subtotal * tax_rate # バグがある行の直前
(Pdb) p subtotal
540
(Pdb) p tax_rate
0.1
(Pdb) c
54.0
ポイントとして、開発中はpdb.set_trace()
を入れて集中的に調べ、原因が分かったら削除する運用が便利です。
うっかり本番コードに残さないように注意してください。
実行を再開/終了する(c/q)
停止中にc
(continue)で次のブレークポイントまたはプログラム終了まで進みます。
q
(quit)でデバッガとプログラム全体を終了します。
q
は実行を打ち切る強い操作なので、処理の途中で終了してよいと確信できるときだけ使います。
よく使うpdbコマンド一覧(最低限)
まずは最低限覚えると効果が高いコマンドの俯瞰です。
コマンド | 目的 | 使いどころ |
---|---|---|
n | 次の行へ進む(関数呼び出しは飛ばす) | 外側の流れを素早く確認 |
s | 次の行へ進む(関数呼び出しの中へ入る) | 関数内部を詳細に追う |
l | 近辺のソースを表示 | 現在位置の行番号と文脈を確認 |
p /pp | 変数の表示/整形表示 | 値の確認、データ構造の把握 |
b | ブレークポイント設定 | 任意の行・関数で停止 |
clear | ブレークポイント削除 | 調査完了後に整理 |
r | 現在の関数から復帰(戻り)まで進む | 長い関数の残りをスキップ |
c | 次の停止点まで継続 | 止めたい場所まで一気に進む |
q | 即時終了 | 調査中止/無限ループ脱出 |
h | ヘルプ | コマンドの意味を確認 |
以下ではそれぞれを短い例で補足します。
ステップ実行(n: next / s: step)
n
は呼び出し元の関数の中だけを一行ずつ実行し、s
は関数呼び出しに差し掛かったとき、その中へ入ります。
(Pdb) l
12 if __name__ == "__main__":
13 items = [("pen", 120, 2), ("notebook", 300, 1)]
14 -> print(total_price(items, 0.1))
(Pdb) n # next: total_priceの中へは入らず、呼び出しを1行で完了
54.0
-- Return --
...
(Pdb) s # step: 呼び出しの中へ入る
> .../buggy_checkout.py(1)total_price()
-> def total_price(items, tax_rate):
関数の外側をなぞりたいときはn
、中身を詳しく追うならs
という使い分けを覚えておくと効率的です。
現在位置を確認する(l: list)
l
は現在行の前後を表示します。
行番号が分かるので、ブレークポイント設定にも役立ちます。
(Pdb) l
6 subtotal = 0
7 for name, price, qty in items:
8 subtotal += price * qty
9 -> total = subtotal * tax_rate # バグのある行
10 return round(total, 2)
変数を表示する(p/pp)
p
で値を表示し、pp
は辞書やリストを整形して見やすく出力します。
(Pdb) p items
[('pen', 120, 2), ('notebook', 300, 1)]
(Pdb) pp {'subtotal': 540, 'tax_rate': 0.1}
{'subtotal': 540, 'tax_rate': 0.1}
ブレークポイントを設定/確認/削除(b / clear)
b
で設定、引数なしのb
で一覧、clear
で削除します。
行番号の代わりに関数名でも指定できます。
(Pdb) b 9
Breakpoint 1 at .../buggy_checkout.py:9
(Pdb) b
Num Type Disp Enb Where
1 breakpoint keep yes at buggy_checkout.py:9
(Pdb) b total_price
Breakpoint 2 at .../buggy_checkout.py:1
(Pdb) clear 1
Deleted breakpoint 1
条件付きブレークポイントはb 8 if qty > 1
のように書きます。
条件がTrueになったときだけ停止するため、ループの特定ケースの切り分けに有効です。
関数呼び出しから戻る(r: return)
現在の関数の戻りまで一気に進み、戻り値が分かった時点で止まります。
(Pdb) r
-- Return --
> .../buggy_checkout.py(10)total_price()->54.0
-> return round(total, 2)
(Pdb) p _
54.0
_
は直近の評価結果や戻り値を参照できる便利なショートカットです。
困ったらヘルプを見る(h)
h
で全体のヘルプ、h b
のように特定コマンドのヘルプを見られます。
コマンド名が分からないときはまずh
が近道です。
(Pdb) h b
b(reak) [ ([filename:]lineno | function) [, condition] ]
With a line number argument, set a break there. If a function name is
supplied, set a break at first executable line of that function.
初心者がつまずきやすい点とコツ
実際に使うときに戸惑いやすいポイントを、短い状況別のコツとしてまとめます。
最初の行で止まったらcで例外発生まで進める
python -m pdb
で起動すると、最初の行で必ず止まります。
すぐに調べたい箇所が決まっているならc
で進め、例外が出るならその直前で自動的に停止します。
「まずエラー発生箇所まで一気に進める→周辺でステップ実行」の手順が効率的です。
条件付きブレークポイント(b 行 if 条件)を試す
毎回同じループで止まってしまって進めにくいときは、条件付きにして対象だけに絞ります。
例えば、個数qty
が2個以上のときだけ止めるなら次のようにします。
(Pdb) b 8 if qty >= 2
Breakpoint 1 at .../buggy_checkout.py:8
(Pdb) c
> .../buggy_checkout.py(8)total_price()
-> subtotal += price * qty
(Pdb) p name, qty
('pen', 2)
大量データの中から不良ケースだけを効率よく再現できるため、原因の切り分けが一気に進みます。
無限ループで止めたい時はCTRL+Cかqで終了
無限ループや時間のかかる処理に入ってしまった場合、Ctrl+C
で割り込みを送るとその場でpdbが起動します。
停止後に動作を調べてc
で継続、またはq
で終了できます。
Windowsでは通常Ctrl+C
で同様に割り込み可能です。
データ更新の途中でq
を使うと中途半端な状態が残る可能性があるため、必要に応じて後処理やクリーンアップを検討してください。
まとめ
pdbはインストール不要で使えるPython標準の対話的デバッガであり、一行ずつの実行・その場での変数確認・柔軟なブレークポイントという3点が核となります。
最初はpython -m pdb
で起動し、l
で位置を確認、b
で止めたい場所を指定、n
/s
で進め、p
/pp
で値を見るという基本の流れを繰り返すのが王道です。
原因の見当がついている箇所にピンポイントで入りたいときはpdb.set_trace()
を挿入し、条件付きブレークポイントで再現条件を絞ると調査がさらに効率化します。
迷ったらh
でヘルプを見て、その場で試しながら理解を深めていきましょう。
printデバッグからの卒業に、まずは本記事の手順を真似してpdbに触れてみてください。