home changes contents help options

211:pythonスクリプトをデバッグする

Python デバッガ pdb

Python には pdb というデバッガが付属しています。

例として次のようなコード spam.py をデバッグしてみます。 x の初期値を 6 とし 3, 2, 1, 0 で割り算していきます。 x の値は for 文が回るたびに 2, 1, 1 となり、4 周目の 0 で割り算された時点で例外 ZeroDivisionError? が発生します。 この例外は処理されることは無く、異常終了します。

# coding: utf-8

def div(x, y):
    return x / y

def main():
    x = 6
    for i in [3, 2, 1, 0]:
        x = div(x, i)
    return x

if __name__ == '__main__':
    main()

デバッグを開始する

スクリプトのデバッグを開始するには、次のようにします。

C:\spam>python -m pdb spam.py
> c:\spam\spam.py(3)<module>()
-> def div(x, y):
(Pdb)

インタラクティブ上から、起動することもできます。 pdb.run メソッドに実行したいコードを文字列で渡します。

>>> import pdb
>>> import spam
>>> pdb.run('spam.main()')
> <string>(1)<module>()
(Pdb)

また、デバッガを立ち上げてはいないときに、異常終了に遭遇した場合にも、後からデバッガを立ち上げて原因の調査を行うことが可能です。 pdb.pm 関数を使用することで事後解析モードで立ち上がります。

>>> import spam
>>> spam.main()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "spam.py", line 9, in main
    x = div(x, i)
  File "spam.py", line 4, in div
    return x / y
ZeroDivisionError: integer division or modulo by zero
>>> import pdb
>>> pdb.pm()
> c:\spam\spam.py(4)div()
-> return x / y
(Pdb)

プログラムを進める

C:\spam>python -m pdb spam.py
> c:\spam\spam.py(3)<module>()
-> def div(x, y):

としてスクリプトのデバッグをはじめたものとします。

pdb プロンプトが立ち上がった直後は、一番最初の文である 3 行目の def div(x, y') で動作が止まっています。

現在箇所の周辺のソースコードを確認するには list コマンドを用います。

(Pdb) list
  1     # coding: utf-8
  2
  3  -> def div(x, y):
  4         return x / y
  5
  6     def main():
  7         x = 6
  8         for i in [3, 2, 1, 0]:
  9             x = div(x, i)
 10         return x
 11

現在箇所周囲のソースコードが表示されます。現在箇所は -> で示されます。

プログラムの実行を進めるには step コマンドを使います。

(Pdb) step
> c:\spam\spam.py(6)<module>()
-> def main():

div 関数の定義が実行され、次の文である 6 行目の def main() に移動しました。さらに進めます。ほとんどのコマンドは最初の一文字でも動作します。 step コマンドの場合は s です。

(Pdb) s
> c:\spam\spam.py(12)<module>()
-> if __name__ == '__main__':

12 行目の if 文に到達しました。何度も同じコマンドを実行する場合はただ Enter キーを押すだけでいいようになっています。前回入力した step コマンドが再度実行されます。

(Pdb)
> c:\spam\spam.py(13)<module>()
-> main()
(Pdb)
--Call--
> c:\spam\spam.py(6)main()
-> def main():

12 行目の main() に到達し、 main 関数が呼び出されました。これにより 6 行目に飛びました。

一行ずつ進めるコマンドのほかに、今の関数が終了し帰るまで続行してくれるコマンドもあります。 return コマンドがそうです。 ここで return をおこなうと、 main 関数が終了するまで進められます。

(Pdb) return
--Return--
> c:\spam\spam.py(9)main()->None
-> x = div(x, i)

None が帰ってきました!? さらに進めてみることにします。

プログラムの最後まで進めてしまうコマンドもあります(正確にはブレークポイントまでです、後述)。 continue コマンドです。

(Pdb) continue
Traceback (most recent call last):
  File "C:\python26\lib\pdb.py", line 1283, in main
    pdb._runscript(mainpyfile)
  File "C:\python26\lib\pdb.py", line 1202, in _runscript
    self.run(statement)
  File "C:\python26\lib\bdb.py", line 368, in run
    exec cmd in globals, locals
  File "<string>", line 1, in <module>
  File "spam.py", line 13, in <module>
    main()
  File "spam.py", line 9, in main
    x = div(x, i)
  File "spam.py", line 4, in div
    return x / y
ZeroDivisionError: integer division or modulo by zero
Uncaught exception. Entering post mortem debugging
Running 'cont' or 'step' will restart the program
> c:\spam\spam.py(4)div()
-> return x / y

ゼロ除算例外で異常終了し、トレースバックの事後解析モードに入りました。 例外の送出先である 4 行目に移動しています。

状況の確認

現在箇所を確認するため、先ほども出てきた list コマンドを実行してみます。

(Pdb) list
  1     # coding: utf-8
  2
  3     def div(x, y):
  4  ->     return x / y
  5
  6     def main():
  7         x = 6
  8         for i in [3, 2, 1, 0]:
  9             x = div(x, i)
 10         return x
 11

現在の箇所は -> で示され 4 行目であることがわかります。

この時点での変数の内容を確認するには p コマンドを用います。

(Pdb) p x
1
(Pdb) p y
0

この時点での div 関数内の変数 x が 1 、 y が 0 であることがわかります。 y が 0 のためゼロ除算エラーが起こったことが予想されます。

スタックトレースを表示するには where コマンドを使用します。

(Pdb) where
  c:\python26\lib\pdb.py(1283)main()
-> pdb._runscript(mainpyfile)
  c:\python26\lib\pdb.py(1202)_runscript()
-> self.run(statement)
  c:\python26\lib\bdb.py(368)run()
-> exec cmd in globals, locals
  <string>(1)<module>()
  c:\spam\spam.py(13)<module>()
-> main()
  c:\spam\spam.py(9)main()->None
-> x = div(x, i)
> c:\spam\spam.py(4)div()
-> return x / y

> で示されている x = div(x, i) の箇所が現在のフレームです。 フレームを移動するには up, down コマンドを用います。

(Pdb) up
> c:\spam\spam.py(9)main()->None
-> x = div(x, i)
(Pdb) l
  4         return x / y
  5
  6     def main():
  7         x = 6
  8         for i in [3, 2, 1, 0]:
  9  ->         x = div(x, i)
 10         return x
 11
 12     if __name__ == '__main__':
 13         main()
[EOF]
(Pdb) p x
1
(Pdb) p i
0

up コマンドで一つ古いフレームに移動しました。 main 関数内の変数 x と i の値がそれぞれ 1, 0 であることがわかります。

デバッガを終了するには quit コマンドを使用します。

(Pdb) quit
Post mortem debugger finished. The spam.py will be restarted
> c:\spam\spam.py(3)<module>()
-> def div(x, y):
(Pdb)

C:\spam>

ブレークポイントの設置

step, next, return コマンドでプログラムを進めるのは手間がかかります。かといって continue コマンドを使用すると プログラムは最後まで走ってしまいます。確認したい箇所がわかっている場合、そこにブレークポイントを設置するとよいでしょう 各種コマンドでプログラムが進んでいる最中でも、ブレークポイントに到達するとデバッガは処理を中断し、そこで停止します。

ブレークポイントを設置するには break コマンドを用います。 9 行目にブレークポイントを設置するには次のようにします。

C:\spam>python -m pdb spam.py
> c:\spam\spam.py(3)<module>()
-> def div(x, y):
(Pdb) break 9
Breakpoint 1 at c:\spam\spam.py:9

この状態では continue コマンドを実行しても 9 行目に到達した時点で停止してくれるようになります。

(Pdb) continue
> c:\spam\spam.py(9)main()
-> x = div(x, i)
(Pdb) p i, x
(3, 6)

初めて到達した時点での main 関数内 x, i 変数の値が 3, 6 であることがわかります。

(Pdb) continue
> c:\spam\spam.py(9)main()
-> x = div(x, i)
(Pdb) p i, x
(2, 2)

2度目の到達時点での x, i の値が 2, 2 であることがわかります。

設定したブレークポイントを確認しなおすには break コマンドを引数なしで実行します。

(Pdb) break
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at c:\spam\spam.py:9
        breakpoint already hit 2 time

1 番のブレークポイントが spam.py の 9 行目にあり、このブレークポイントには 2 回到達したことがわかります。

設定したブレークポイントを一時無効にするには disable, 有効にするには enable コマンドを用います。削除するには clear コマンドを用います。

(Pdb) clear 1
Deleted breakpoint 1

参考資料

pdb は、ここで説明した以外の機能も多数持っています。詳しくは Python ライブラリリファレンスを参照してください。