フリーキーズ | 独学プログラミング

Pythonデバッガーのpdbの使い方とデバッグ方法いろいろ

最終更新日
【画像】Pythonデバッガーのpdbの使い方とデバッグ方法いろいろ
実行時の環境

Python(+ pyenv) 3.11.5

Pythonだけでなく、プログラミングをしている際に必ずする作業がデバッグです。Pythonであればprint()関数やpprintモジュールを使うことで、変数や式の値を表示できます。
ただし、この方法だと毎回実行しなくてはいけません。

そこで、今回はPythonにデフォルトで組み込まれているデバッガーpdbをご紹介します。
ブレイクポイントと呼ばれる「止めるポイント」を設置することによって、段階的なコード内探索が可能になります。

pdbチートシート

pdbのコマンドはあまり使うことがないため、先に一覧としてまとめておきます。

コマンドショートコマンド動作
argsa引数を表示
breakb行数指定でブレイクポイントを置く
clearclブレイクポイントの削除
continuec, cont実行を続行する
enable-指定のブレイクポイントを有効化
disable-指定のブレイクポイントを無効化
listl現在行前後のソースコード表示
nextn関数の次の行に至るかreturnされるまで実行
downd階層を下げる
upu階層を上げる
steps現在行を実行
wherew現在の行を表示
pp-式の値を表示(= pretty-print
restart-現在デバッグ中のプログラムを再実行
returnrreturnされるまで実行
quitqデバッガの終了
helphコマンド一覧の表示
alias-エイリアスの作成
bt-whereのエイリアス
commands-指定のブレイクポイント番号にコマンドのリストを指定
condition-指定のブレイクポイント番号に条件式を追加
debug-コードをステップ実行する再帰的デバッガに入る
display-式の変更を表示
exitqデバッガの終了
ignore-指定数、ブレイクポイントを通過
interact-インタプリタを起動
jumpj次に実行する行の指定
longlistll現在の関数、またはフレームのソースコード表示
p-式の値を表示
retvalrv最後にreturnされた値を表示
run-現在デバッグ中のプログラムを再実行
source-式のソースコード取得
tbreak-一度で破棄されるブレイクポイントを設置
unalias-エイリアスの削除
undisplay-現在のフレームで指定の式を表示しない
untilunt現在行、または指定行より先になるまで実行
whatis-型を表示

pdbとは

pdbは、Pythonの標準ライブラリとして提供されているデバッガーです。インタラクティブなデバッグ機能を提供しています。

主な機能は以下です。

  • ブレイクポイントの設置
  • ステップ(段階的)実行
  • スタックフレーム調査
  • ソースコードの表示・評価

プログラミングにおけるデバッグツールの必要性

Pythonに限らず、プログラミングしているときには常にデバッグが必要となります。自身のコードが正しく機能していることを確認しながらでないと、思わぬバグを引き起こす可能性があります。

冒頭でも説明したように、単純にprint()関数でも事足ります。
任意の箇所にprint()を挟み込むことで、その箇所で期待通りの値が代入されているかを確認できます。しかしながら、print()関数はデプロイ時に放置されてしまったり、思ったとおり動かないときに再実行が必要だったりと、使い勝手が最高と言いづらい難点もあります。

これを効率的に支えてくれるのがpdbです。

pdbのセットアップ

pdbは標準ライブラリのため、わざわざインストールする必要がありません。
単純にbreakpoint()を使うか、pdbをimportして使います。

デバッガセッションの開始

スクリプト内で起動したいときは以下のように書きます。

import pdb


def say_hello(name: str):
    return f'Hello {name}!'

name = 'Taro'
pdb.run("print(f'Say hello to {name}: {say_hello(name)}')")
import pdb; pdb.set_trace()


def say_hello(name: str):
    return f'Hello {name}!'

name = 'Taro'
print(f'Say hello to {name}: {say_hello(name)}')
breakpoint()

def say_hello(name: str):
    return f'Hello {name}!'

name = 'Taro'
print(f'Say hello to {name}: {say_hello(name)}')

または、先頭からデバッグを開始する場合、以下のコマンドで実行できます。

python -m pdb xxx.py

このようにすることで、pdbのデバッガー画面が表示されます。簡単に実行を確認するため、ここではcontinueを意味するcを実行してみましょう。

> myscript.py(3)<module>()
-> def say_hello(name: str):
(Pdb) c
Say hello to Taro: Hello Taro!

基本的なpdbコマンド

pdbには、多くのコマンドが存在いますが、頻繁に使用するコマンドはその一部です。
ここでは基本的なコマンドを覚えて、まずはpdbでデバッグすることに慣れましょう。

ソースコードを表示するlist(l)

pdblistコマンド(ショートコマンドはl)は、現在の行の周りのソースコードを表示するために使われます。開発者がデバッグ中に自分のコードが実行されているコンテキストを理解するのに役立ちます。

以下、listを利用する例です。
まずは簡単なコードを書いてみます。

def add(a, b):
    result = a + b
    return result

def main():
    x = 5
    y = 7
    breakpoint() # ここにブレイクポイント
    sum_value = add(x, y)
    print(sum_value)

if __name__ == "__main__":
    main()
python sample.py

開始されたデバッガーのプロンプトで、list、またはlを実行すると以下のように表示されます。

-> sum_value = add(x, y)
(Pdb) l
  4
  5  	def main():
  6  	    x = 5
  7  	    y = 7
  8  	    breakpoint()
  9  ->	    sum_value = add(x, y)
 10  	    print(sum_value)
 11
 12  	if __name__ == "__main__":
 13  	    main()
[EOF]

->が表示されている行が現在実行している行です。listは、現在実行している行の前後数行を表示します。

次の行まで実行を続けるnext(n)

pdbnextコマンド(ショートコマンドはn)は、現在の関数の次の行に到達するか、リターンするまで実行を続けるために使われます。
基本的にnextコマンドはコードを一行ずつステップ実行できますが、関数に入ることはありません。(stepコマンドとの違い)

nextコマンドの例は以下です。

def add(a, b):
    result = a + b
    return result

def main():
    x = 5
    y = 7
    breakpoint()
    sum_value = add(x, y)
    print(sum_value)

if __name__ == "__main__":
    main()
python sample.py

まずはlコマンドで現在行を確認しましょう。

(Pdb) l
  4
  5  	def main():
  6  	    x = 5
  7  	    y = 7
  8  	    breakpoint()
  9  ->	    sum_value = add(x, y)
 10  	    print(sum_value)
 11
 12  	if __name__ == "__main__":
 13  	    main()
[EOF]

9行目にいることがわかります。

次に、nextnをタイプして実行します。デバッガーはsum_value = add(x, y)の行に移動しますが、まだ実行はしません。

(Pdb) n
> sample.py(10)main()
-> print(sum_value)

next、またはnをもう一度入力すると、デバッガはsum_value = add(x, y)の行を実行します。(add関数には入らず実行されるだけ)。

(Pdb) n
12
--Return--
> sample.py(10)main()->None
-> print(sum_value)

このように、nextコマンドを使えばadd関数内を探索することなくコードをステップ実行できます。もしnextの代わりにstepコマンドを使った場合、add関数の中に入って、一行ずつデバッグしていくことになります。

関数呼び出しに進むstep(s)

pdbstepコマンド(ショートコマンドs)を使うと、関数やメソッドに「入り込んで」その内部実行を調査できます。この特徴はnextコマンドとは対照的です。

前の例を流用してstepコマンドを説明します。

def add(a, b):
    result = a + b
    return result

def main():
    x = 5
    y = 7
    breakpoint()
    sum_value = add(x, y)
    print(sum_value)

if __name__ == "__main__":
    main()
python sample.py

早速デバッガープロンプトでstepコマンドを実行してみましょう。

(Pdb) st
--Call--
> sample.py(1)add()
-> def add(a, b):

まずはadd()関数内に入りました。再実行しましょう。

(Pdb) s
> sample.py(2)add()
-> result = a + b

さらに実行を進めます。

(Pdb) s
> sample.py(3)add()
-> return result
(Pdb) s
--Return--
> sample.py(3)add()->12
-> return result

このようにstepコマンドを使うことで、関数やメソッドの内部を綿密に調べることができ、コードの各部の流れや動作を確実に理解できるようになります。

プログラムの実行を再開するcontinue(c)

pdbcontinueコマンド(ショートコマンドc)を使うと、次のブレイクポイントにぶつかるまでプログラムの実行を再開できます。追加のブレークポイントが設定されていなければ、プログラムは完了するまで実行されます。

先ほどのコードを少し修正してcontinueコマンドを説明します。

def add(a, b):
    result = a + b
    return result

def main():
    x = 5
    y = 7
    breakpoint() # 1つ目のブレイクポイント
    sum_value = add(x, y)
    print("Sum:", sum_value)
    breakpoint() # 2つ目のブレイクポイント
    print("Done.")

if __name__ == "__main__":
    main()
python sample.py

早速デバッガーに入ってみましょう。
まず、最初のブレイクポイントでに位置していることがわかります。

> sample.py(9)main()
-> sum_value = add(x, y)

この時点でcontinueまたはcを実行すると、プログラムは次のブレイクポイントに達するまで実行されます。ここではprint文の後に2つ目のブレイクポイントを置いたので、デバッガーはそこで停止します。

(Pdb) c
Sum: 12
> sample.py(12)main()
-> print("Done.")

ここでもう一度continueコマンドを実行すれば、残りのブレイクポイントがないため完了まで実行されます。

(Pdb) c
Done.

continueコマンドは、スクリプト内に複数のブレイクポイントがあり、その間にあるコードを1行ずつステップ実行することなく素早く移動したい場合に便利です。

ブレークポイントを設定するbreak(b)

pdbbreakコマンド(ショートコマンドb)を使うと、特定の行や関数にブレイクポイントを設置できます。設置したブレイクポイントに達すると実行が一時停止され、その時点から変数の検査や式の評価、コードのステップスルーができるようになります。

stepコマンドの例は以下です。

def add(a, b):
    result = a + b
    return result

def subtract(a, b):
    difference = a - b
    return difference

def main():
    x = 5
    y = 7
    breakpoint()
    sum_value = add(x, y)
    print("Sum:", sum_value)
    difference_value = subtract(x, y)
    print("Difference:", difference_value)

if __name__ == "__main__":
    main()
python sample.py

デバッガーに入ったら、breakコマンドでブレイクポイントを設置してみましょう。

(Pdb) break subtract
Breakpoint 1 at sample.py:5

ここでcontinueまたはcを実行すると、プログラムはsubtract関数にぶつかるまで実行されます。

(Pdb) c
Sum: 12
> sample.py(6)subtract()
-> difference = a - b

また、行番号でブレイクポイントを設定可能です。たとえば、16行目(差分が出力される行)にブレイクポイントを設定したい場合は次のように入力します。

(Pdb) break 16
Breakpoint 2 at sample.py:16
(Pdb) c
> sample.py(16)main()
-> print("Difference:", difference_value)

breakコマンドに引数を渡さなければ、設定したブレイクポイントを一覧表示できます。

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at sample.py:5
	breakpoint already hit 1 time
2   breakpoint   keep yes   at sample.py:16
	breakpoint already hit 1 time

breakコマンドを使えば、一時停止する箇所を効果的にコントロールでき、特定の箇所でコードの状態や振る舞いを調べられます。

デバッガーを終了するquit(q)

デバッガーを終了したい場合はquit(ショートコマンドq)を入力すれば終了します。

(Pdb) q
~中略~
bdb.BdbQuit

使い方を調べるhelp(h)

他のコマンドを調べたいときはhelpコマンド(ショートコマンドh)を使いましょう。
引数を渡さなければ一覧表示されます。

(Pdb) h

Documented commands (type help <topic>):
========================================
EOF    c          d        h         list      q        rv       undisplay
a      cl         debug    help      ll        quit     s        unt
alias  clear      disable  ignore    longlist  r        source   until
args   commands   display  interact  n         restart  step     up
b      condition  down     j         next      return   tbreak   w
break  cont       enable   jump      p         retval   u        whatis
bt     continue   exit     l         pp        run      unalias  where

Miscellaneous help topics:
==========================
exec  pdb

各コマンドを調べるときは、helpコマンドに引数として渡しましょう。

(Pdb) h where
w(here)
        Print a stack trace, with the most recent frame at the bottom.
        An arrow indicates the "current frame", which determines the
        context of most commands.  'bt' is an alias for this command.

高度な機能とコマンド

先述した基本的な機能とは別に、pdbにはより高度なデバッグ機能があります。デバッグに高機能さを求めるケースは、頻繁ではありませんが、特に障害発生時の原因調査で有効です。

エンジニアになってから数年が経ち、アプリケーションだけでなくインフラやネットワークなど広範囲で世話するようになると、障害発生時の原因調査は大切なタスクとして降り掛かってきます。
そういったときでも慌てないように、プログラムを追う方法を見ていきましょう。

条件付きブレイクポイント

pdbでは、特定の条件が満たされた時のみ実行される条件付きブレークポイントを設定できます。大きなデータをループ処理していて、特定の状況下でのみ実行を一時停止させたい場合に便利です。

例えば、数値のリストを処理するループがあり、特定の条件(例えば、ある閾値より大きな数値)に遭遇して停止させたい場合を考えてみましょう。
今回の例では以下のスクリプトを使います。

def process_numbers(numbers):
    for num in numbers:
        if num > 50:
            print(f"High value: {num}")
        else:
            print(f"Regular value: {num}")

def main():
    nums = [10, 25, 60, 45, 80]
    breakpoint()
    process_numbers(nums)

if __name__ == "__main__":
    main()
python condition_sample.py

上記コマンドでデバッガーに入ると、process_numbers(nums)の行で停止します。
ここで、50以上の数値が処理されたときに実行を一時停止したいとします。process_numbers関数の内部、if num > 50の行に条件付きブレイクポイントを設定しましょう。

(Pdb) b 4, num > 50
Breakpoint 1 at condition_sample.py:4

cで実行を続行しましょう。すると、下記のようにnumが50を越えるときに停止します。

(Pdb) c
Regular value: 10
Regular value: 25
> condition_sample.py(4)process_numbers()
-> print(f"High value: {num}")

もう一度cを実行し、再開しましょう。

(Pdb) c
High value: 60
Regular value: 45
> condition_sample.py(4)process_numbers()
-> print(f"High value: {num}")

上記のように、以後の処理が走りますが、最後に80があるためもう1度停止します。

例外発生後のデバッグに役立つpost_mortem

post_mortem関数を使うと、例外が発生した後にその例外を調べられます。特に、予期せぬ例外が発生した際、何が問題だったのかを理解するのに便利です。

以下にpost_mortemの使用例を示します。

import pdb


def divide_numbers(a, b):
    return a / b

def main():
    x = 5
    y = 0
    try:
        result = divide_numbers(x, y)
        print(result)
    except Exception as e:
        print(e)
        pdb.post_mortem()

if __name__ == "__main__":
    main()

このスクリプトを実行してみましょう。

python error_sample.py

以下のように、例外が発生し、エラー内容と共にデバッガーが起動します。

division by zero
> error_sample.py(5)divide_numbers()
-> return a / b

この場合、divide_numbers関数の内部、ちょうど例外が発生した行に位置するところから開始されます。他のpdbコマンドを使って、変数の検査、式の評価、コード内のナビゲーションをしてデバッグすることとなります。

post_mortem関数は、コード中に予期せぬ例外が発生し、例外が発生した瞬間のプログラムの状態を検査したい場合に有用です。

次に実行する行を設定するjump(j)

jumpコマンド(ショートコマンドj)を使うと、次に実行する行を設定できます。コードの特定の部分をスキップしたり、前のポイントに戻って再実行したい場合に便利です。

ただし、jumpコマンドにはいくつか注意すべき点があります。

  1. 一般に、前方(つまり現在の行よりも後ろ)にジャンプするのが安全です。後方へのジャンプやループ内へのジャンプは、変数スコープやループの働きにより、予測不可能な結果を招く可能性があります。
  2. **関数の外にジャンプできません。**対象となる行は現在の関数内になければなりません。
  3. try/exceptやブロックの中や外へのジャンプは望ましくない動作につながる可能性があります。

これらの注意点を念頭に置いて、実際にjumpコマンドを使ってみましょう。

def simple_function():
    print("Start of function")
    breakpoint()
    print("Middle of function")
    print("End of function")

if __name__ == "__main__":
    simple_function()
python jump_sample.py

デバッガーに入って始めてください。
例えば、「関数の途中」のprint文をスキップして、「関数の終わり」の行に直接ジャンプしたいとしましょう。目的の行番号を指定してジャンプできます。

(Pdb) jump 5
> jump_sample.py(5)simple_function()
-> print("End of function")

ここでcを実行すると、最後のprint実行が確認できます。

(Pdb) c
End of function

これがもし、関数外などジャンプ制限に引っかかった場合は、以下のようにエラーが出ます。

(Pdb) jump 6
*** Jump failed: line 6 comes after the current code block

変数のきれいに表示するpp

ppコマンドは"pretty print"の略です。辞書やリストのようなデータ構造を、より読みやすく整形して表示するのに役立ちます。

例を使ってppコマンドを紹介ます。

def display_data():
    data = {
        "name": "Hanako",
        "age": 30,
        "address": {
            "pref": "Fukuoka",
            "zip": "810-0001"
        },
        "hobbies": ["reading", "gardening", "shogi"]
    }

    breakpoint()
    print("Done.")

if __name__ == "__main__":
    display_data()
python pp_sample.py

変数dataの内容を調査したい場合は、ppコマンドを使うことできれいに表示できます。

(Pdb) pp data
{'address': {'pref': 'Fukuoka', 'zip': '810-0001'},
 'age': 30,
 'hobbies': ['reading', 'gardening', 'shogi'],
 'name': 'Hanako'}

printであるpコマンドを使った場合は以下のようになります。ppのほうが見やすいことは一目瞭然です。

(Pdb) p data
{'name': 'Hanako', 'age': 30, 'address': {'pref': 'Fukuoka', 'zip': '810-0001'}, 'hobbies': ['reading', 'gardening', 'shogi']}

ppコマンドは、大きくネストされたデータ構造を扱う場合に有用となります。

単純に変数の中身を見るにはprint関数を使えばよいのですが、構造化されたデータの場合は読みやすさの点からppの方が望ましいでしょう。

まとめ

ここまで、pdbについて紹介してきましたが、詳細なデバッグをするプログラミングは比較的大規模なものとなります。
デバッグだけに頼らず、単体テストやlinterを駆使して高品質なコードを書けるようになることが大切です。

pdbは標準ライブラリということもあり、他のライブラリと統合されていることもあるので、pdbの使い方は覚えておいて損ありません。この機会にたくさん触って、手持ちスキルにしてしまいましょう。

関連するコンテンツ