エラーは出ていないのに、計算結果だけがどうもおかしい。そんな場面に出くわしたことはありませんか。
多くの人はここでprintを書き足します。変数の中身を表示して、どこで値が狂うのかを探すやり方です。
私も最初はそうでした。ただ、この方法はすぐに苦しくなります。
printを足しては実行し、確認したら消して、また別の場所に足す。調べたい変数が増えるほど、この往復だけで時間が溶けていきます。
実はPythonには、そのための道具が最初から入っています。pdbというデバッガです。
この記事では、pdbの使い方をゼロから順番に整理していきます。追加のインストールは要りません。
pdbは、Pythonに最初から入っているデバッガ¶
まずは言葉の整理から始めましょう。デバッガとは、プログラムを途中で止めて中身を覗くための道具のことです。
pdbはその標準ライブラリ版で、Pythonを入れた時点で使えるようになっています。pipで何かを追加する必要はありません。
printとの違いは、止まったその場で好きなことを聞ける点にあります。変数を表示するだけでなく、その場で計算したり、1行ずつ先へ進めたりできます。
printデバッグでは、何を見たいのかをあらかじめ決めてからコードに書き込むことになります。予想が外れていたら、書き直してもう一度実行するしかありません。
一方のpdbは、止まってから考えられます。見たいものを、見たくなった時点で聞ける。 これがいちばん大きな違いです。
breakpoint()を1行書くだけで止まる¶
使い方はとても簡単です。止めたい場所に breakpoint() と書くだけで済みます。
importも要りません。次のコードを手元のファイルに保存して、そのまま実行してみてください。
def greet(name):
message = "Hello, " + name
breakpoint()
return message
print(greet("Python"))
実行すると、途中で (Pdb) という表示が出て入力待ちになります。ここがデバッガの中です。
この状態で変数名を打てば、その中身がそのまま返ってきます。
> bp.py(4)greet()
-> return message
(Pdb) p name
'Python'
(Pdb) p message
'Hello, Python'
(Pdb) c
Hello, Python
p は表示するための命令で、c は続きを実行するための命令です。最後まで進めば、プログラムはいつもどおり終わります。
止まる位置に注目してください。breakpoint() を書いた行ではなく、その次の行で止まっています。
つまり、その行を実行する直前の状態が見えているということです。ここを勘違いすると、変数がまだ入っていないと慌てることになります。
覚える操作は、まず5つだけで足りる¶
pdbには30を超える命令がありますが、最初から全部を覚える必要はありません。実務でよく使うのは、ほんの数個です。
私が普段の調査で叩いているものだけをまとめておきます。
| 命令 | 読み方 | 何をするか |
|---|---|---|
n |
next | 次の行へ進む。関数呼び出しは中に入らず飛ばす |
s |
step | 次の行へ進む。関数呼び出しの中まで入る |
c |
continue | 次に止まる場所まで一気に走らせる |
p 式 |
その場で式を評価して表示する | |
l |
list | いま止まっている位置の前後のコードを表示する |
q |
quit | デバッガを終了する |
迷ったら l を打ってみてください。いま自分がどこにいるのかが、矢印つきで表示されます。
関数の中身に興味がないときは n、中で何が起きているか知りたいときは s。この2つの使い分けだけ覚えておけば、当面は困りません。
もう少し欲しくなったら、w で呼び出しの経路を、ll で関数全体のコードを表示できます。
PYTHONBREAKPOINT=0でまとめて無効にできる¶
breakpoint() には、printには無い便利な性質があります。環境変数で挙動を切り替えられる点です。
PYTHONBREAKPOINT に0を指定して実行すると、breakpoint() は何もせずに素通りします。
PYTHONBREAKPOINT=0 python bp.py
先ほどのコードをこの形で動かすと、デバッガに止まらず結果だけが表示されます。消し忘れた breakpoint() があっても、実行が止まってしまうことはありません。
とはいえ、これに頼って消し忘れたまま実行環境へ持っていくのは避けたいところです。デバッグ用の行は、調査が終わったその日のうちに消す。 これは自分との約束にしておくのが安全です。
環境変数の扱いに不安がある方は、こちらで基本から整理しています。【関連記事】環境変数とは?PythonでAPIキーを安全に扱う.envと os.environ の基本を初心者向けに解説
エラーが起きたあとから原因の場所へ飛べる¶
ここからが、pdbのいちばん強力な使い方です。エラーが出てから調べる、事後デバッグと呼ばれるやり方になります。
たとえば、カートの合計金額を計算する次のようなコードを考えます。
def total_price(items):
total = 0
for item in items:
total += item["price"] * item["count"]
return total
cart = [
{"name": "book", "price": 1200, "count": 2},
{"name": "pen", "price": 150, "count": "3"},
]
print(total_price(cart))
これを普通に実行すると、TypeErrorで止まります。数値と文字列を足そうとしているからです。
ただ、エラー文だけでは何番目のデータが悪いのかまでは分かりません。そこで、-c continue を付けてpdb経由で走らせます。
python -m pdb -c continue demo.py
すると最後まで走ったあと、エラーが起きたその瞬間の場所でデバッガが立ち上がります。
TypeError: unsupported operand type(s) for +=: 'int' and 'str'
Uncaught exception. Entering post mortem debugging
> demo.py(4)total_price()
-> total += item["price"] * item["count"]
(Pdb) p item
{'name': 'pen', 'price': 150, 'count': '3'}
(Pdb) p type(item["count"])
<class 'str'>
犯人がその場で分かりました。penのcountだけが文字列になっていた、というだけの話です。
printを1つも書かずにここまで辿り着けるのが、この方法の良いところです。エラーの再現に時間がかかる処理ほど、効果を実感できます。
エラー文を読むのではなく、エラーが起きた瞬間の変数をそのまま見る。 発想を切り替えると、調査は驚くほど短くなります。
エラー文そのものの読み方に不安が残る方は、あわせてこちらもどうぞ。【関連記事】Pythonのエラー文はどこを読めばいい?初心者向けtracebackの見方
私は10年ほどエンジニアとして開発に関わってきましたが、いまでも原因の見当がつかないエラーに当たったときは、まずこの -c continue から始めます。当てずっぽうでprintを置くより、圧倒的に速いからです。
なお -c continue を付けずに python -m pdb demo.py とすると、1行目で止まった状態から始まります。最初から順番に追いかけたいときは、こちらを使ってください。
Python 3.14では動いているプロセスにもつなげる¶
新しい話題もひとつ紹介しておきます。Python 3.14で、pdbに大きな機能が入りました。
すでに動いているPythonのプロセスへ、あとから接続できるようになったのです。プロセスIDを指定して呼び出します。
python -m pdb -p 12345
これはPEP 768で定められた仕組みの上に作られています。プログラムを止めたり入れ直したりせずに、中の様子を確かめられるようになりました。
長時間動き続けるプログラムが固まったときに効いてくる機能です。再起動してしまうと、その瞬間の状態は永遠に失われてしまいます。
ただし万能ではありません。対象のプロセスが通信待ちなどで止まっている場合、接続が効くのは次の処理が動き出したあとになります。
学習中にここまで使う機会は多くないかもしれません。ただ、動いているプログラムに触れずに中を覗ける という発想は、知っておくと将来かならず効いてきます。
printデバッグとpdbは、どちらも使う¶
ここまで読むと、printはもう使わないほうがいいのかと思われたかもしれません。そんなことはありません。
どちらにも向き不向きがあります。場面ごとの目安をまとめておきます。
| 場面 | 向いている方法 | 理由 |
|---|---|---|
| 値を1つだけさっと確認したい | 書いて実行するほうが早い | |
| どこで値が狂うのか分からない | pdb | 止めてから探せる |
| 原因不明のエラーが出た | pdb(事後デバッグ) | 発生地点まで自動で連れて行ってくれる |
| 動かし続けて記録を残したい | logging | あとから見返せる |
| 同じ不具合を二度と出したくない | テストコード | 自動で検出できる |
printを速さで選び、pdbを深さで選ぶ。この感覚で使い分けると、無駄が減っていきます。
そして原因が分かったら、最後にテストを1本書いておくのが理想です。【関連記事】pytest入門|Pythonでテストを書く基本を初心者向けに解説
なお、printを使うときも f"{変数=}" と書けば変数名ごと表示できます。手軽さを残したまま見やすくする書き方です。【関連記事】Pythonのf-stringとは?デバッグが驚くほど楽になる!
よくあるつまずきと対処¶
最後に、実際に触りはじめた人がひっかかりやすい点をまとめます。ここを知っておくと、初日の戸惑いが減ります。
| つまずき | 起きること | 対処 |
|---|---|---|
| 変数名がpdbの命令と同じ | n や c が命令として解釈される |
p n のように p を付けて表示する |
c を打っても終わらない |
ループの中に breakpoint() がある |
q で抜けるか、書く場所を変える |
| 止まった行の変数が空 | その行はまだ実行されていない | n で1行進めてから確認する |
| 出力が混ざって読めない | 標準出力とデバッガの表示が重なる | 対象の処理だけを切り出して実行する |
| 消し忘れて実行環境へ出す | 処理が途中で止まる | コミット前に breakpoint で検索する |
辞書まわりのエラーで止まったときは、こちらの記事も参考になるはずです。【関連記事】PythonでKeyErrorが出る理由とは?辞書dictで初心者がハマるポイント
まずは、いま書いているコードのどこかに breakpoint() を1行足してみてください。実行して p で変数を1つ表示するだけで、感覚はつかめます。
デバッガは、上級者だけの道具ではありません。 むしろ、仕組みがまだよく分かっていない初心者ほど、中を覗ける価値は大きいはずです。
ここまでお読みいただきありがとうございました。