概要
pdb を利用してみる。
ドキュメント
公式ドキュメント「pdb — The Python Debugger」に概要が書いてある。
使い方
スクリプトを直接起動してデバッグする方法と、インタラクティブshell で起動する方法があるが、ここでは、スクリプトを起動してデバッグする方法で書く。
以下がデバッグするスクリプトのサンプル「even.py」。
自分の場合、スクリプトを直接デバッグすることがあまりなく、デバッグするのはライブラリとかをunittest経由でデバッグすることがが多いのでサンプルは unittest で書いている。
以下の例はクラス内の関数が偶数だけ返す所でバグがあり、奇数を返すようになっている。そんなに良い例ではないかも。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest
class Sample(object):
"""
テスト対象のクラス
"""
def to_even(self, lst):
"""
リストを渡すと、偶数だけにして返す
"""
# 意図的にバグらせていて、奇数しか返さない
evenlst = [v for v in lst if v % 2 == 1]
return evenlst
class TestSample(unittest.TestCase):
"""
テストクラス
"""
def test_main(self):
clazz = Sample()
# 0 から 10 のリストを生成 [0, 1, 2, .....]
lst = xrange(10)
evenlst = clazz.to_even(lst)
# 偶数かテストする
for item in evenlst:
num = item % 2
self.assertEqual(0, num)
if __name__ == '__main__':
unittest.main()
とりあえず実行してみると以下のようにエラーになる。
> python even.py
F
======================================================================
FAIL: test_main (__main__.TestSample)
----------------------------------------------------------------------
Traceback (most recent call last):
File "even.py", line 33, in test_main
self.assertEqual(0, num)
AssertionError: 0 != 1
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
pdbを起動してみる。起動は以下のようにする。
python -m pdb even.py
起動すると pdb プロンプトが起動する。
> /path/to/even.py(3)<module>()
-> import unittest
(Pdb)
上記の出力から「3行目」の「import」で実行が停止しているのがわかる。
「list」で停止位置の周辺ソースが表示される。
(Pdb) list
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 -> import unittest
4
5
6 class Sample(object):
7 """
8 テスト対象のクラス
9 """
10 def to_even(self, lst):
11 """
(Pdb)
行数とともに、現在の停止場所に「->」がある。
「list」のようにデバッグに利用できるコマンドの主な物は以下になる。コマンドには省略形が存在する物もある。
コマンド | 省略形 | 概要 |
---|---|---|
help | h | コマンドのヘルプを表示。 「help」でコマンド一覧、 「help コマンド名」でコマンドのヘルプ が表示される。 |
where | w | スタックトレースを表示 |
down | d | スタックトレース中に1レベル下げる |
up | u | スタックトレース中に1レベル上げる |
step | s | 現在行を実行し、次の行に進む 次の行が関数の場合、関数内で停止 |
next | n | 現在行を実行し、次の行に進む 次の行が関数でも、関数内では停止しない |
return | r | 現在の関数から抜ける |
list | l | 現在行周辺のソースを表示 デフォルトは11行表示する |
args | a | 現在関数の引数一覧を表示 |
p 式 | 式内容を表示 | |
pp 式 | 式内容を pprint で表示 | |
quit | q | デバッガ終了 |
現在行が「import」なので「n(ext)」で進める。
間違えて「s(tep)」すると「import」を実行する Python のライブラリ内に入る。その場合は「r(eturn)」とかすると戻ってこれる。
(Pdb) n
> /path/to/even.py(6)<module>()
-> class Sample(object):
(Pdb)
class の行になるが、実行されるわけではない。
再度「n」するが、この「n」は一度実行すると次回は「リターンキー」で同じ事を実行する。
(Pdb)
> /path/to/even.py(18)<module>()
-> class TestSample(unittest.TestCase):
(Pdb)
「c(ontinue)」すると、停止するまで実行する。
(Pdb) c
F
======================================================================
FAIL: test_main (__main__.TestSample)
----------------------------------------------------------------------
Traceback (most recent call last):
File "even.py", line 33, in test_main
self.assertEqual(0, num)
AssertionError: 0 != 1
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
The program exited via sys.exit(). Exit status: True
> /path/to/even.py(3)<module>()
-> import unittest
ブレークポイント
通常 step 実行とかしないで、ブレークポイントを利用する。ブレークポイント関連のコマンドは以下。
コマンド | 省略形 | 概要 |
---|---|---|
break 行数 or 関数名 | b | 指定行数にブレークポイントを付ける |
tbreak 行数 or 関数名 | 一時的ブレークポイントを付ける 一回通過すると消える |
|
clear 行数 or bp番号 | cl | ブレークポイントを削除 |
disable bp番号 | ブレークポイントを停止 | |
enable bp番号 | ブレークポイントを有効化 | |
ignore bp番号 回数 | ブレークポイントを回数分無視する | |
condition bp番号 | ブレークポイントの状態を確認 | |
commands bp番号 | ブレークポイントを表示 | |
continue | c | ブレークポイントで停止してる場合、実行を継続 |
「33行目」にブレークポイントを設定し「c」して「l」で確認してみる。
(Pdb) b 33
Breakpoint 1 at /path/to/even.py:33
(Pdb) c
> /path/to/even.py(33)test_main()
-> for item in evenlst:
(Pdb) l
28 # 0 から 10 のリストを生成 [0, 1, 2, .....]
29 lst = xrange(10)
30 evenlst = clazz.to_even(lst)
31
32 # 偶数かテストする
33 B-> for item in evenlst:
34 num = item % 2
35 self.assertEqual(0, num)
36
37
38 if __name__ == '__main__':
(Pdb)
ここで「evenlst」の内容を表示してみる。
(Pdb) p evenlst
[1, 3, 5, 7, 9]
(Pdb)
バグっている事がわかる。
「n」を2回して「l」して「p」してみる。「p」で複数表示の場合はカンマで区切る。
(Pdb) p item,num
(1, 1)
(Pdb)
バグ原因が判明したら「q」などで終了すれば良い。
まとめ
他者の作成したライブラリ等はソース修正するとまずいので、 print を入れることができない場合が多く、そういう場合は pdb は便利。
本当はライブラリ内で Exception が発生したりするので、結構デバッグは大変だったりする。
自作のスクリプトの場合は、普通は pdb を使うよりも、いかに良くテストを書くかの方が重要な気がしている。pdbを使わなければいけないのはテストが下手だと思った方が良い。