paloma blog

NWエンジニアやってます。主に自宅環境のお遊びを書きます。Pythonもちょっと。タイトルは好きなカクテルから。

初めてpythonのpdbでデバッグをやってみた

前の映画情報取得ツールを製作していた時のこと。

OMDB APIを取得するツールをpythonで自作する - paloma blog

全処理を一旦書き上げたのですが実行しても出力されません。

(movies) masashi@PC-ubuntu:~/tests$ python getinfo.py 
(movies) masashi@PC-ubuntu:~/tests$ 

しかしプロンプト内でやるとちゃんと返ってきます。

>>> main()

Title: Argo
Year: 2012
Rated: R
Released: 12 Oct 2012
Runtime: 120 min

どうにもわからないのでデバッグをすることにしました。

pdbモジュール

よくやるデバッグと言えばprintを仕込んで変数内容を確認するというものですが、
私もpython歴数年になりますのでそろそろデバッグもスマートに行いたいところです。

pdbモジュールの記事がよく出てくるのでこちらを使ってデバッグしてみます。

pdb --- Python デバッガ — Python 3.9.4 ドキュメント

モジュール読み込み

コードの中に仕込む方法が公式ドキュメントなどで出てきますが、引数で読み込ませる方法のほうが簡単そうだったのでこちらで試してみます。

-m pdbでモジュールを読み込ませてコードを起動します。

(movies) masashi@PC-ubuntu:~/tests$ python -m pdb getinfo.py
> /home/masashi/tests/getinfo.py(1)<module>()
-> import requests
(Pdb) 

するとこんな感じでpdbの対話モード入ります

ヘルプ

(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

pdbを使うのに戸惑う理由がオプションの多さだと思います。
どれを使えばよいか迷いますが、とりあえずは変数が想定通り格納されているかという観点で考えると
p(rint)だけ使えばOKということになります。

ちなみコードはこちら

llで全行出力されます。
長文のコードだと使えないかもしれませんが今回のは短いです。

テスト用にちょっといじってあります。
実はこの時点でおかしいところがわかりますね。

(Pdb) ll
  1  ->   import requests
  2    import sys
  3    import json
  4    # import configure
  5    import re
  6    
  7    # KEY = configure.APIKEY
  8    # url = 'http://www.omdbapi.com/'
  9    # gettitle = '+'.join(sys.argv[1:])
 10    # getinfo = ''.join([url, '?apikey=', KEY, '&t=', gettitle])
 11    with open('sample.json', 'r') as f:
 12        movieinfo = f.read()
 13    
 14    def main():
 15    
 16        dataload = json.loads(movieinfo)
 17        datadump = json.dumps(dataload, indent=0)
 18    
 19        shapedata = re.sub('\n.\n|"|{|}|\[|\]', "", datadump)
 20        shapedata = re.sub(',\n', "\n", shapedata)
 21    
 22        print(shapedata)
 23    
 24    if __name__ == 'main':
 25        main()

ちなみにlだと現在の行周辺が出力されます。

-> import sys
(Pdb) l
  1    import requests
  2  ->   import sys
  3    import json
  4    # import configure
  5    import re
  6    
  7    # KEY = configure.APIKEY
  8    # url = 'http://www.omdbapi.com/'
  9    # gettitle = '+'.join(sys.argv[1:])
 10    # getinfo = ''.join([url, '?apikey=', KEY, '&t=', gettitle])
 11    with open('sample.json', 'r') as f:

n(ext)

nは1行ずつ読み込みます。
空エンターでも同じ挙動をしますね。

(Pdb) n
> /home/masashi/tests/getinfo.py(2)<module>()
(Pdb) n
> /home/masashi/tests/getinfo.py(3)<module>()
-> import json
(Pdb) 
> /home/masashi/tests/getinfo.py(5)<module>()
-> import re
(Pdb) 
> /home/masashi/tests/getinfo.py(11)<module>()
-> with open('sample.json', 'r') as f:
(Pdb) 
> /home/masashi/tests/getinfo.py(12)<module>()
-> movieinfo = f.read()
(Pdb) 
> /home/masashi/tests/getinfo.py(14)<module>()
-> def main():

p(rint)

pで変数を出力できます。
被疑の行まで来たらpで中身を確認すれば所謂printのデバッグになりますね。

今回はサンプルのjsonデータを読み込ませてますが、ちゃんと格納されてます。

(Pdb) p movieinfo
'{"Title":"Argo","Year":"2012","Rated":"R","Released":"12 Oct 2012","Runtime":"120 min","Genre":"Biography, Drama, Thriller","Director":"Ben Affleck","Writer":"Chris Terrio, Tony Mendez, Joshuah Bearman","Actors":"Ben Affleck, Bryan Cranston, John Goodman",
...

main実行の行まで来たのでここで手動で実行してみます。

> /home/masashi/tests/getinfo.py(24)<module>()->None
-> if __name__ == 'main':
(Pdb) main()

Title: Argo
Year: 2012
Rated: R
Released: 12 Oct 2012
Runtime: 120 min
...

ちゃんと出力できてますね。
となるとターミナルから起動、出力周りが怪しいということになりますね。

原因

 24  ->  if __name__ == 'main':
 25        main()

mainを実行されるときの行がミスしてました。

mainは__main__と書かないと認識されませんね。
ここは久しぶりに空で書いたのでルールを忘れてました。

というわけで対象行を修正して無事にデバッグ完了です。

 24  ->  if __name__ == '__main__':
 25        main()
(movies) masashi@PC-ubuntu:~/tests$ python getinfo.py

Title: Argo
Year: 2012
Rated: R
Released: 12 Oct 2012
Runtime: 120 min
...

おまけ r(eturn)

nだと1行ずつで時間がかかってしまいましが、rだと「現在の関数が返るまで実行を継続」する様です。
現在の関数が何を指しているか不明ですが私の場合だとmain関数のところまで飛びました。

-> import requests
(Pdb) l
  1  ->   import requests
  2    import sys
  3    import json
  4    # import configure
  5    import re
  6    
  7    # KEY = configure.APIKEY
  8    # url = 'http://www.omdbapi.com/'
  9    # gettitle = '+'.join(sys.argv[1:])
 10    # getinfo = ''.join([url, '?apikey=', KEY, '&t=', gettitle])
 11    with open('sample.json', 'r') as f:
(Pdb) r
--Return--
> /home/masashi/tests/getinfo.py(24)<module>()->None
-> if __name__ == 'main':
(Pdb) l
 19        shapedata = re.sub('\n.\n|"|{|}|\[|\]', "", datadump)
 20        shapedata = re.sub(',\n', "\n", shapedata)
 21    
 22        print(shapedata)
 23    
 24  ->   if __name__ == 'main':
 25        main()
[EOF]

まとめ

というわけで初めてpdbを使ったデバッグを行いました。
いつもprintを仕込んでいましたが、コードを汚さずにデバッグできるのは便利ですね。
とりあえずpだけ覚えておけば私の規模のコードだと問題なくできました。

pdbには他にもいろいろオプションがあるので、また詰まる時が来たら他のも使ってみたいと思います。
関数の中に入るコマンドもあった記憶がありますが、今回は確認できなかったので次回チャレンジですね。