9. 落穂ひろい

9.1. おまけ: デバッグの手法

デバッグの手法についてです. 長いプログラムを書いていると,思った通りの動作を してくれない場合があります. これらのバグは,たいていの場合,ユーザーの プログラミング言語に対する

  • 認識不足

  • 不注意

  • 思い込み

などが原因です. (プログラムは,通常書いたとおりに動きます.思った通りに動かないだけです)

このような,動作を修正するための方法は, デバッグ と呼ばれ,様々な技法が存在します.

ここでは,簡単な幾つかの方法について紹介します.

9.1.1. print 文による情報表示

これは,割りと汎用的なデバッグ技法で,ほとんどの プログラミング言語で使用可能な方法です. プログラムは,書いたとおり動くものなので,プログラムの 動作途中の変数の状態などを print() 関数などを 使って,コンソールに書き出すことでチェックします.

たとえば,''1 から 10 までの和を求めたい'' という状況が 発生したとして,下記のようなプログラムを記述したとしましょう.

sum = 0
for idx in range(10):
    sum = sum + idx

このコードを実行すると sum に 45 という数値が代入されますが, 求める答えと違うことになります.(正解は 55)

このコードの,どこにバグがあるのかを考えるために

sum = 0
for idx in range(10):
    sum = sum + idx
          print('idx: ', idx, 'sum: ', sum)
print(sum)

というようにループ内部に print文を導入して ,変数 sumidx の状態を表記させてやります.すると

>>> sum = 0
>>> for idx in range(10):
...    sum = sum + idx
...    print 'idx: ', idx, 'sum: ', sum
idx:  0 sum:  0
idx:  1 sum:  1
idx:  2 sum:  3
idx:  3 sum:  6
idx:  4 sum:  10
idx:  5 sum:  15
idx:  6 sum:  21
idx:  7 sum:  28
idx:  8 sum:  36
idx:  9 sum:  45
>>> print sum
45

といったように,変数 'idx' が,0 から 9 まで変化していることが わかります. 問題としては ''1 から 10 まで和'' を求めたいという話なので, 対応するためには,

>>> sum = 0
>>> for idx in range(10):
...    sum = sum + (idx+1)
...    print('idx+1: ', (idx+1), 'sum: ', sum)

のように修正してやる必要がありそうです.(他にもやりかたはあります)

この例は, range(M)() 関数の生成するリストが, 0 から M-1 までの整数値を返してくることを誤解しているような 場合に起きうる間違いです.

このように, print() 関数を用いることで, 状況を確認することができます.

9.1.2. デバッガによる確認

print() 関数によるデバッグは有効ですが, 大量のデータのデバッグや,オブジェクトの内部状態を知りたいなど といった場合には,なんらかの工夫が必要になってきます. 継続的に変数やオブジェクトの状況を観察しながら,デバッグを 行いたいといった場合に有効な手法が デバッガ を用いた デバッグになります. C などのコンパイラ言語を用いた場合のデバッガに gdb などが あるように python には pdb というデバッガが標準モジュールとして 添付されます.

9.1.2.1. スクリプトのデバッグ

自分の書いたスクリプトをデバッグする場合には, シェルのコマンドラインから下記のように,pdb を モジュールとして読み込んで実行させれば OK です. 例題として,上で使用した '1〜10 までの和を求める為の計算' プログラムを 例題とし,これが,hoge.py という名前だったとしましょう.

sum = 0
for idx in range(10):
    sum = sum + idx
          print('idx: ', idx, 'sum: ', sum)
print(sum)

スクリプトのデバッグを実行するには,コンソールで,

$ python -m pdb hoge.py

と実行すれば

> /Users/shouno/hoge.py(1)<module>()
-> sum = 0
(Pdb)

と今までと違ったプロンプトが出ることがわかります. このプロンプトが Pdb デバッガのプロンプトです. Pdb は立ち上がるとスクリプトファイルを読み込み, スクリプトの最初の行で,実行を停止して, ユーザーからのコマンド待ち受け状態になります. ここでは,いくつかのコマンドの簡単な紹介をします.

  • l (list) コマンド

    list コマンドはソースコードを表示します.

    (Pdb) list
    1  ->  sum = 0
    2      for idx in range(10):
    3          sum = sum + idx
    4          print('idx: ', idx, 'sum: ', sum)
    5      print(sum)
    [EOF]
    

    ちょうど,矢印 -> の位置が現在の実行している行です.

    また,引数として,表示させたい行を指定できます. 3行目付近を見たければ

    (Pdb) list 3
    

    のように指定できます.

  • n (next) コマンド

    現在の指している行を実行して,次の行に移ります. 上述の例で next コマンドを実行した場合,変数 sum に 0 を設定し,2行目に移動します. したがって,変数名を知りたければ,通常の python の コマンドラインと同様に変数名を打ち込んでやれば 変数値を表示してくれます.

    (Pdb) next
    (Pdb) sum
    0
    
  • b (break) コマンド

    break コマンドは,任意の行で実行を止めることが可能です. 下記の continue コマンドと一緒に使うことで,スクリプトの 任意の行まで実行を行い,止めることが出来るようになります. 例えば,4行目まで実行して,そこで止めたい場合, break コマンドの引数として行番号を指定をします. list コマンドで見ると 行頭に B が付きます.

    (Pdb) break 4
    Breakpoint 1 at /Users/shouno/hoge.py:4
    
    (Pdb) list 4
    1      sum = 0
    2  ->  for idx in range(10):
    3          sum = sum + idx
    4 B        print('idx: ', idx, 'sum: ', sum)
    5      print(sum)
    [EOF]
    
  • c (continue) コマンド

    continue コマンドは,実行を継続します.break point が 設定されていない場合は,プログラムが停止するまで 実行しますので,エラーの位置を知りたい場合は,これを 最初に使うことになります.

    上記の例で,continue コマンドを実行すると,4行目に break point が設定されているので,その直前の 3行目まで実行します.

    (Pdb) c
    > /Users/shouno/hoge.py(4)<module>()
    -> print 'idx: ', idx, 'sum: ', sum
    

    つまり,idx = 0 の時点で,止まることになります.

    次のループ (idx=1) に移りたい場合は, 同様に continue コマンドを実行します.この場合も, やはり,4行目のブレークポイントの直前で 停止する事になります

9.1.2.2. 関数のデバッグ

それでは,関数やオブジェクトメソッドのデバッグは, どのようにすればよいでしょうか?

クラッシュするプログラムを調べるためには, 関数を実行する前に pdb をモジュールとして import して, pdb.pm() 関数をつかって,事後解析デバッグを行います. mymodule が自分で書いたモジュールで,モジュール内の test() という 関数をデバッグする場合,

>>> import pdb
>>> import mymodule
>>> mymodule.test()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "./mymodule.py", line 4, in test test2()
  File "./mymodule.py", line3, in test2
    print spam
  NameError: spam
>>> pdb.pm()
> ./mymodule.py(3).test2()
-> print(spam)
(Pdb)

となって,クラッシュした場所の位置でデバッガに入ることができます. ここで print などで,変数内容などを詳しく調べることができます.

9.1.2.3. pdb についての詳細情報

より詳細に関しては,

などを参照してください.

個人的には pdb3 あたりを好んで使っています. また,Visual Studio Code や PyCharm で実行するとここらへんの不満がほぼ解消されます.