デバッガによる不具合の調査
単価120×個数3と、単価200×個数2の合計は760になるはずですが、試料は325を返します。Pythonのpdbで先頭行に停止した状態から始め、条件付きブレークポイント、ステップ実行、監視式、呼出し履歴で原因を特定します。
説明
合計が期待した値にならないときは、入力、関数へ渡した値、戻り値、累積値のどこで差が生じるかを確認します。デバッガは実行を途中で止め、変数や呼出し元を調べるための道具です。
単価120×個数3と、単価200×個数2の合計は760になるはずですが、試料は325を返します。Pythonのpdbで先頭行に停止した状態から始め、条件付きブレークポイント、ステップ実行、監視式、呼出し履歴で原因を特定します。
1停止中のコードを確認
現在はcart.pyの先頭行を実行する直前です。行番号付きで全体を表示します。
list 1, 121〜12行目を表示line_totalが一行の金額、totalが合計を計算します。7行目でline_totalの戻り値をamountへ加えます。->は次に実行する位置です。
2条件付きで停止する場所を指定
7行目に到達し、quantityが3のときだけ停止するブレークポイントを置きます。
break 77行目に停止点, quantity == 3個数3のときだけ停止Breakpoint 1が設定されます。条件は7行目に到達した時点の変数で評価されます。まだこの行の加算は実行しません。
3指定した行まで実行
continueで実行を再開します。
continue条件に一致する停止点まで進むtotalの7行目で止まります。この表示はamountへ加えた後ではなく、これからline_totalを呼ぶ位置です。
4代入前の値と監視式
引数と累積値を表示し、このフレームのamountを監視します。;;はpdbの複数コマンドの区切りです。
p (price, quantity, amount)現在の局所変数を表示 ;; display amountこのフレームの値を監視単価120、個数3、累積値0です。displayは停止時に式を再評価し、値が変わった場合に表示します。観察で状態を変えないよう、ここでは単純な変数参照だけを使います。
5関数の中へ入る
stepで呼出し先のline_totalへ進みます。
step呼出し先へ入る--Call--が表示され、line_totalへ入りました。呼出し前後だけを見たい場合のnextと異なり、stepでは呼出し先の内部を調べられます。
6計算行へ進む
nextで同じフレーム内の次の行へ進みます。
next同じ関数の次の行へ進む2行目のreturn price + quantityを実行する直前です。ここで値を確認すれば、計算前の引数と式を対応させられます。
7引数と仕様からの期待値
関数の引数を表示し、仕様どおり単価と個数を掛けた値も評価します。
args現在の関数の引数 ;; p price * quantity仕様に基づく計算引数は120と3で、掛け算なら360です。ソースの+は単価と個数を足しており、仕様の掛け算と一致しません。期待値を誤った関数の再呼出しで作らず、仕様の計算と照合しています。
8呼出し元までの履歴
whereで呼出し履歴を表示します。
where呼出し履歴を表示cart.pyの11行目からtotalの7行目を経由し、line_totalの2行目へ来ています。先頭のbdb.runはデバッガの実行部分です。>が現在選択中のフレームで、各フレームは別の局所変数を持ちます。
9呼出し元の変数を確認
upでtotalのフレームを選び、amountを確認してからdownでline_totalへ戻ります。
up呼出し元のフレームを選ぶ ;; p amount呼出し元の局所変数 ;; down呼出し先へ選択を戻す呼出し元のamountはまだ0です。フレームの選択は観察する局所変数を切り替える操作であり、処理を実行して過去へ戻す操作ではありません。
10戻り値が決まるまで実行
returnで現在の関数が戻るところまで進めます。
return関数の戻りまで実行--Return--と123が表示されます。120+3の値であり、期待した360ではありません。ここで入力の取り違えではなく、関数内の式に原因があると確認できます。
11戻り値を累積へ反映
特殊な__return__で戻り値を確認し、nextで呼出し元の続きを進めます。
p __return__停止中の戻り値 ;; next呼出し元で加算した後へ進む123が表示され、その後amountの監視値も0から123へ変わります。関数の戻り値が呼出し元の加算へ使われたことが分かります。
12残りの処理を実行
continueで残りを実行します。
continue残りを実行して出力を確認最終出力は325です。二行目も200+2=202なので、123+202になっています。修正対象はline_totalの+で、仕様どおり*へ変更し、入力120・3なら360、合計なら760になることを再確認する必要があります。
まとめ
停止位置は通常、その行を実行する前です。変数の値を代入前後で混同しないよう確認します。
stepは呼出し先へ入り、nextは同じフレームで進みます。フレーム選択と実行の進行は別の操作です。
入力・期待値・戻り値・累積値を照合すると、どの式で仕様と異なる値になったかを特定できます。
操作履歴と解説を振り返れます。
Python 3.15 / pdb
list 1, 121 -> def line_total(price, quantity): 2 return price + quantity 3 4 def total(rows): 5 amount = 0 6 for price, quantity in rows: 7 amount += line_total(price, quantity) 8 return amount 9 10 rows = [(120, 3), (200, 2)] 11 result = total(rows) 12 print(result)
break 7, quantity == 3Breakpoint 1 at /lab/cart.py:7
continue> /lab/cart.py(7)total() -> amount += line_total(price, quantity)
p (price, quantity, amount) ;; display amount(120, 3, 0) display amount: 0
step--Call-- > /lab/cart.py(1)line_total() -> def line_total(price, quantity):
next> /lab/cart.py(2)line_total() -> return price + quantity
args ;; p price * quantityprice = 120 quantity = 3 360
where/opt/homebrew/Cellar/python@3.15/3.15.0_1/Frameworks/Python.framework/Versions/3.15/lib/python3.15/bdb.py(904)run() -> exec(cmd, globals, locals) /lab/cart.py(11)<module>() -> result = total(rows) /lab/cart.py(7)total() -> amount += line_total(price, quantity) > /lab/cart.py(2)line_total() -> return price + quantity
up ;; p amount ;; down> /lab/cart.py(7)total() -> amount += line_total(price, quantity) 0 > /lab/cart.py(2)line_total() -> return price + quantity
return--Return-- > /lab/cart.py(2)line_total()->123 -> return price + quantity
p __return__ ;; next123 > /lab/cart.py(6)total() -> for price, quantity in rows: display amount: 123 [old: 0]
continue325
演習完了