« 2007年06月 | メイン | 2007年09月 »

2007年08月 アーカイブ

2007年08月14日

Pythonでtypoを素早く察知する方法

先日「スクリプト言語は便利だけど、 長時間計算した後で綴り間違い(typo)で落ちたりすると やる気がなくなるよね」 という話を聞きました。 たしかに、綴り間違いで落ちるのは悲しいです。 せめて「代入されたけども読み出されていない変数」とか 「代入される前に読み出されている変数」が出力できれば、 早めに綴り間違いに気づくことができますね。

そこで「先日公開したCodeHackモジュールを使えば簡単にできるよ」と言いながら作ったのが今回紹介するモジュールです。 CodeHack 0.05に同梱しました。 CodeHack 0.06に同梱しました。

使い方は簡単です。 たとえば下のような関数fooを定義したとします。

def foo(left, top, right, bottom):
    "function for test"
    width = right - left
    hieght = top - botom
    print height, width
よく見ると2カ所ほど綴り間違いがあるのですが、 この関数をcodehack.var_warningの中のcheckfuncに渡してみると 下のようにその綴り間違いの変数が表示されます。関数fooを実行していないので、たとえ実行に1時間かかるような関数であってもチェックは一瞬です。
>>> from codehack.var_warning import checkfunc
>>> checkfunc(foo)
variable 'botom' is global
variable 'height' is global
variable 'hieght' is not used
variable 'bottom' is not used
実は綴り間違いの判定は何もしていなくて、 単純に「値が代入されたのに読み出されていない」 という変数を「not used」と表示しているだけです。 また、逆の「値が代入されていないのに読み出されている変数」は Pythonでは「グローバル変数と見なして外のスコープを見に行く」 という仕様になっているのでglobalと表示しています。(注1)

ただ、そのままですと'range'や'set'なども警告されたり、 自分で定義したグローバル関数を使うとそれも警告されたり、 とうっとうしいことになるので、 警告しない名前のリストをsurpressという引数で指定できるようにしてあります。 デフォルトでは組み込みの名前(rangeとか)がsurpressに入っています。

def checkfunc(func,
          print_globals = True,
          print_not_used = True,
          print_func_name = False,
          surpress = _default_surpress):

CodeHackモジュールを使っている部分は、実はたいしたことはしていません。 下のようにLOAD_FAST(ローカル変数のロード)という オペコードの引数(変数名になっている)を集めているだけです。 codehack.util.analyzeはオペコード間の依存関係(どのオペコードが積んだ値をどのオペコードが消費するか)のトレースをするのですが、それも必要ないのでわざわざ外しています。

    import util
    opcodes = util.analyze(func, do_trace=False)

    loaded_vars = set(op.argstr
                for op in opcodes
                if op.opname == "LOAD_FAST")
下のようにして、モジュールの中の全ての関数をチェックすることもできます。 すでに自分で書いたモジュールがあれば試してみると面白いでしょう。 checkallはprint_globalsをFalseにしてあるので「代入されているけど読み出されていない」変数だけが出ます。
>>> from codehack.var_warning import checkfunc, checkall
>>> import urllib
>>> checkfunc(urllib.urlopen)
variable 'FancyURLopener' is global
variable '_urlopener' is global
>>> checkall(urllib.__dict__)
* function splitnport
* function getproxies
* function retrieve
variable 'msg' is not used
variable 'garbage' is not used
(snip)
ぜひ試してみて、ご意見ご感想やパッチを頂けるとうれしいです。

(注1) 実際はもちろんグローバルの名前空間にいきなり行くわけではなくて、 スコープがネストしている場合には1枚外のスコープへ行きます。 globalと呼んでいるのは単にバイトコードがLOAD_GLOBALだからだったりします。 いい名称があれば変えたほうがいいかもしれません。 また、関数がネストしている場合は内側の関数で使われる変数を 「使われる」と判断することができずに警告を出してしまいます。 これはdisしてみるとわかるのですが、内側の関数はコードオブジェクトとして 定数テーブルに埋め込まれているため、 外側の関数のバイトコードを見ただけでは内側で何を使うかがわからない ことが原因です。定数テーブルにコードオブジェクトがあるかどうかをチェックして 再帰的にたどるようにすればいいだろうと思いますが…それは今後の反響次第と言うことで。

About 2007年08月

2007年08月にブログ「西尾泰和のブログ @ Cybozu Labs」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2007年06月です。

次のアーカイブは2007年09月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。