【Python】breakpointとpdbでエラー直前の変数を調べる

PythonのTopに戻る

失敗する直前で止めて値を見る

IndexErrorの原因を調べるためにprintを何行も追加する代わりに、breakpoint()で処理を止め、pdbから変数を調べられる。停止中は現在の関数だけでなく呼び出し元の状態も確認できる。まず、再現できる小さな入力で失敗する場所を絞り、問題のある式の直前に停止点を置く。

以下のdebug_example.pyは、リストの最後の値を取りたいのにlen(rows)を添字にしてしまった、意図的に失敗する例である。通常の端末でpython debug_example.pyとして実行すると、(Pdb)という入力待ちが現れる。NotebookやIDEでは独自のデバッガーが使われる場合があるため、ここでは端末上のpdbを対象にする。

添字とリストの長さをその場で調べる

debug_example.py

def last_value(rows):
    index = len(rows)
    breakpoint()
    return rows[index]


print(last_value([10, 20, 30]))

停止したら、p index、p len(rows)、p rowsを順に入力し、最後にcで続行する。下は実際の実行から、変数の表示と最終エラーを抜粋したもの。停止位置のファイルパスとトレースバックの途中は省略している。例は続行するとIndexErrorになり、終了コードは1となる。

実行結果(抜粋)

(Pdb) 3
(Pdb) 3
(Pdb) [10, 20, 30]
IndexError: list index out of range

調べた事実をコードの修正へ戻す

長さが3のリストの有効な添字は0、1、2であり、indexが3なので範囲外と分かる。デバッガー上だけで変数を書き換えて動かすと、ソースの誤りは残る。最後の値を取る目的ならrows[-1]へ修正し、空のリストは用途に沿った例外にする。次のfixed_example.pyは停止点を外した修正版である。

fixed_example.py

def last_value(rows):
    if not rows:
        raise ValueError("rows must not be empty")
    return rows[-1]


assert last_value([10, 20, 30]) == 30
assert last_value([10]) == 10
try:
    last_value([])
except ValueError as error:
    assert str(error) == "rows must not be empty"
else:
    raise AssertionError("empty input was accepted")
print(last_value([10, 20, 30]))

実行結果

30

まず覚えるpdbの操作

p 式は式の値を表示する。lは現在位置の周辺のソース、wまたはwhereは呼び出し履歴を表示する。nは現在の関数の次の行まで進み、sは呼び出した関数の中へ入る。cは次の停止点まで続け、qはデバッグを終了する。コマンドの詳細はhで確認できるため、最初からすべて覚える必要はない。

upとdownで呼び出し履歴上のフレームを移動すると、呼び出し元の変数を調べられる。失敗した関数に渡された値が既におかしい場合は、呼び出し元へ戻ると原因を追いやすい。wで全体を見て、対象のフレームを選び、pで必要な値だけを見る順にすると調査の範囲を絞れる。

デバッガーの入力は単なる閲覧画面ではなく、Pythonの式や処理を実行できる。pの式でも関数を呼べば副作用が起きる場合がある。ファイルへの書き込みや外部通信を含む式を、値を見たいだけのつもりで実行しない。まず変数の値、型、長さといった読み取り中心の確認から始める。

停止しない・入力待ちで止まる場合

breakpoint()の動作はPYTHONBREAKPOINTやsys.breakpointhookによって変更できる。例えば無効化されていれば、呼び出しても止まらない。例の確認ではpdb.set_traceを使う設定を明示した。動かないときは、実行しているファイルが保存したものと同じか、停止点まで到達しているか、環境設定で動作が変わっていないかを順に確認する。

自動実行やCIに停止点を残すと、入力待ちになったり標準入力がなく失敗したりする。調査が済んだら停止点を外し、再現条件を自動テストへ移すと同じ問題の再発を検知できる。デバッガーで一度動いたことと、毎回正しく動くことは別なので、空入力や1件だけの入力も修正版で確認する。

pdbの表示位置や機能はPythonの版によって変わる。この記事の実行結果はCPython 3.12.14のものであり、新しい版のコマンドや停止位置をこの環境で確認したものではない。共有する画面やログには変数の秘密情報が含まれないかも確認しておこう。

実行環境と関連情報

掲載コードはLinux上のCPython 3.12.14で実行した。OS固有のファイル操作や対話環境の違いは、本文に記した条件に従って扱う。

関連:pytestで正常系と異常系の自動テストを始める / 例外を握りつぶさないtry・except・else・finallyの使い分け

PythonのTopに戻る