写真提供:マイナビニュース

写真拡大

前回から、書かれたプログラムが期待どおりに動いているかどうかを確認する手法について扱っています。今回はデバックについて解説していきます。

○Printデバッグ

まず最初は一番シンプルなprintデバッグです。printデバッグという名前から想像できるかもしれませんが、その使い方はいたって単純です。簡単に言ってしまうと、プログラムの途中にプログラムとは直接関係のないprint文を「今どこを実行しているかを連番などで表示」したり、「怪しい変数の中身を表示」したりするためにはさみます。

たとえば以下のコードがあるとしましょう。

このコードではプログラムの1行ごとにどこまで進んでいるかをprint文で表示させています。きちんと読めばわかりますがe = 5 / (c - d)のところにバグがあるので、実行させるとここで止まります。

print(5)相当の表示はあるものの、print(6)の表示はありません。そのため、5と6の間に問題があることがわかります。なお、今回はスタックトレースにもろに原因が書かれていますね。問題が発生したコードがどのように呼びだされたかというピンポイントの場所は特定できます。もう少し複雑なプログラムになったときに「どういう流れでそれに至ったか」を調べるときに使ったりします。

問題箇所を特定できた次のステップとして、今度は変数をprintしてみてどう動いているのかを特定する作業に入ります。今回は単純なプログラムなのでそれは割愛します。

printデバッグはやや場当たり的な手法となりますのでその使用に賛否両論がありますが、「一番簡単」「環境に依存しない」「Pythonはコンパイルに時間がかからず、小さいスクリプトだと害はほとんどない」という理由で、巨大なコードを調べる場合以外は問題ないと私は考えています。

○pdbを使ったインタラクティブなデバッグ

デバッガはデバッグ作業を手助けしてくれるツールです。先ほどのようにprintデバッグするのでもいいですが、より詳細な動きを調べたかったり、コードを変更することができない場合などといった状況でprintデバッグより便利に使えます。

pdbは「シェルでインタラクティブにデバッグする方法」と「デバッグ用のコードを直接書く方法」の2つがあります。まず前者を試してみます。以下にバグのあるコードを提示します。

インタラクティブにデバッグする方法ではデバッガPDBに対してコマンドで指令を与えながらプログラムを解析していきます。コマンドにもいろいろあるのですが、いきなり多くを扱うと混乱するので、まずはプログラムを進めるstep、nextそして現在位置の表示をするlistです。

・step: 実行を1行進める
・next: 実行を1行進めるが、関数呼び出しはreturnされるまで一気に進める
・list: 現在実行しているコードの位置を表示

とりあえず、シェルモードで起動してみます。PDBのシェルモードでの起動は以下のように"-m pdb"のオプション付きで行います。

まずはPythonがプログラムを読んでいく処理です。1行目の「divide関数の定義」の位置で止まっています。

試しにstepとlistを使ってみます。どんどんコードを読んでいくことがわかるはずです。なお、コマンドを全文字打ち込んでもかまわないのですが、s、lというように頭文字だけコマンドを打てば動きます。

sだけでも何をやっているか表示をしてくれますので、毎回lを打つ必要はありません。今どこか詳細な確認をしたい場合のみ、lで確認すれば大丈夫です。続けて、どんどんstepしていきましょう。

関数testをCall(呼び出し) しましたね。続けます。

関数の中に入りました。stepを続けます。

divide関数を呼び出し、最終的にreturnし、次のdivide(2,1)にまで進みましたね。毎回sですべての処理を呼ぶのも面倒なので、stepよりももう少し一気に進めるnextを使ってみます。

関数の中身を飛ばしています。sとnの表示の違いをよく見比べるとわかるはずです。続けてバグのある関数呼び出しもnextしてしまいます。

当然ながらエラーが表示されていますね。さて、stepとnextおよびlistがわかったところで新しいコマンドを使ってみます。ブレークポイントの設定breakと、breakポイントかエラーにヒットするまで進めるcontinue、そして変数の中身を確認するprintです。まず、一度デバッグをコマンドquitで抜けてしまいます。

プログラムの場所を指定してブレークポイントをセットすると、そこまでデバッグプログラムを一気に進めてしまうことが可能です。今回は8行目からのdivide関数呼び出しでトラブルが発生しているので8行目にbreakをセットします。breakでブレークポイントをセットし、continueでブレークポイントまで一気に勧めます。

さて、先程はnextで一気に勧めてしまったのですが、今度はstepで慎重に進めてみます。問題の箇所にきたら、printコマンドを使って変数の中身を見てみます。

ZeroDivisionErrorの原因となった割り算の位置で、"p a"として変数aの中身を表示しています。bも同様です。bの値が0であることがわかり、これでなぜZeroDivisionErrorとなったかが詳細に確認できましたね。

なお、今回の例では多くのコマンドを割愛しています。それらについては以下を参照してください。

・26.2. pdb - Python デバッガ

最後にpdb.set_trace()の使い方を説明します。先ほどは行番号を指定してbreakをセットしましたが、毎回この位置のコードをデバッグするのであればプログラム本文にブレークポイントを書くこともできます。

pdb.set_trace()がコード文中に設置されていますね。この状態で、シェルでデバッグしてみます。

continueすると、breakポイントをコマンドでセットしていなくても、このpdb.set_trace()したところで止まっていることがわかります。うまく使うことでデバッグ作業が楽になりますよ。

○pdbを使ったクラッシュ後のデバッグ

先ほどはインタラクティブにプログラム起動時点からデバッグをしましたが、クラッシュ後にデバッグを開始することも可能です。まず、Pythonシェル(PDBのシェルとは違うので注意)に入って、先ほどのプログラムを実行してクラッシュさせます。

エラーが発生しましたね。ここで、pdbモジュールをロードしてpm()関数を実行してみます。

PDBのシェルに入れましたね。先ほどの先頭から実行した場合と異なり、すでにクラッシュした行にいることがわかります。念のためにlistで確認します。

printを使って変数の中身も確認できています。ちなみに、stepやnextに対応する「戻る」はgdbとは違いサポートされていないようです。

次回は、Pythonの命名規則について扱います。

(伊藤裕一)