傀儡師の館.Python

2007/12/01(土)07:19

python 用 lint、Pyflakes vs. pyChecker vs. pylint

Python(214)

Python の文法チェックには何を使う? lint はないの? で Python だと lint 相当のものは何があるということで pyChecker と pylint を取り上げたが、pyflakes もシンプルなので、うるさすぎる警告で肝心な警告を見逃すことがないからよいということで教えていただいた。 Re:pyflakes(11/24) yasusiiさん pyflakes を普通にインストールすると c:\Python25\Scripts などに pyflakes というファイルがあるはずです。これを pflks.py など pyflakes.py 以外の名前にリネームします(中で import pyflakes を実行してるので、pyflakes.py にすると自分自身をインポートしてしまう)。 環境変数 PATH に上記パスを追加、PATHEXT に .py を追加すれば pflks というコマンドとして実行可能になります。(November 27, 2007 02:29:46) 実を言うと、一回試して、ちゃんと動かない止めと思って書かなかったのだった。「pflks.py など pyflakes.py 以外の名前にリネームします(中で import pyflakes を実行してるので、pyflakes.py にすると自分自身をインポートしてしまう)。」、というところで、ろくすっぽ見ずに、これをやって投げてしまっていた。あほな。。。あらためて、yasusiiさんありがとうございます。 ということで、改めて pyflakes についても触れてみる。 pyflakes のコマンドを眺めてみると、最初に標準モジュールの compiler を使って (compiler.parse)、ソースコードをコンパイルしてしまい、構文木を得る。コンパイルエラーが出るようなコードであれば、エラーを表示する。コンパイルに成功した場合には、得られた構文木を pyflakes の checker.Checker にかけるという流れになっているようだ。 エラー処理や複数ファイルの処理をなくして、単純化して、途中の結果を見てみる。 #!/usr/bin/env python #-*- coding: utf-8 -*- import compiler, sys import os #from pyflakes import checker import pyflakes.checker filename = sys.argv[1] print u"ファイル名: %s" % filename print "*" * 20 # ファイルからソースを読み込んで codeString = file(filename, 'U').read() # コンパイルする tree = compiler.parse(codeString) # 出力して、どんな風にコンパイルされているか見てみよう print tree print "*" * 20 w = pyflakes.checker.Checker(tree, filename) w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) for warning in w.messages: print warning print u"メッセージ数: %d" % len(w.messages) そうすると次のような出力が得られる(長いのやるとすごい量になる)。 ファイル名: a.py ******************** Module(None, Stmt([Import([('sys', None)]), Import([('sonnanonaiyo', None)]), Assign([AssName('naiyo', 'OP_ASSIGN')], CallFunc(Getattr(Name('sonnanonaiyo'), 'usoclass'), [], None, None)), Class('class_a', [], None, Stmt([Function(None, '__init__', ['self'], [], 0, None, Stmt([Pass()])), Function(None, 'test', ['self'], [], 0, None, Stmt([Pass()]))])), Function(None, 'test_func', ['x'], [], 0, None, Stmt([Pass()])), Assign([AssName('x', 'OP_ASSIGN')], Const('OK')), Assign([AssName('y', 'OP_ASSIGN')], Const('NG')), Printnl([Name('x')], None)])) ******************** a.py:1: 'sys' imported but unused メッセージ数: 1 実際に pyflakes を実行したときに出力されるのは、最後の「a.py:1: 'sys' imported but unused」だけのシンプルなもの。pyflakes は、***** の部分のように構文解析されたものに対して、チェックしている。ということで、import 文があっても読み込むことはしないし、命令も実行されないので安全。上のチェックは下の意味のないコードにしたものだが、import sonnanonaiyo に対してはチェックがかからない。naiyo = sonnanonaiyo.usoclass() に対しても。要するに構文的に OK で、それが不使用でなければチェックはされない。import sys は、使われていないのでチェック対象になっている。 parser -- Python解析木にアクセスする、 19.3 Python 抽象構文。 import sys import sonnanonaiyo naiyo = sonnanonaiyo.usoclass() class class_a: def __init__(self): pass def test(self): pass def test_func(x): pass x = "OK" y = "NG" print x これを pyChecker で試してみると、 Processing a... ImportError: No module named sonnanonaiyo Warnings... a:1: NOT PROCESSED UNABLE TO IMPORT と、import sonnanonaiyo の時点でエラーを出す。が、それ以上のチェックは行えない。要するに、pyChecker はインポート文を実行しながら動く。つまり、ファイルを読み込むときに実際にインポートしてしまうので (print "OK" は実行されてしまう)、自分で動きを把握してないものに対して使うと、インポートしたときに何があるか分からないのでセキュリティ上はよろしくない。これに対して、pyflakes は実際にはインポートしないので、その点は安全だし、インポートしないだけ軽く動く。逆に言えば、sonnanonaiyo というモジュールがあり、sonnanonaiyo.usoclass() を呼び出すコードがあったとき、usoclass() がないというのを pyChecker は sonnanonaiyo をインポートしているので、正しくあるなしを判定してくれるのに対して、pychecker は読み込まないので、構文的に正しければ、エラーにはならない。 このソースの場合、その点を除けば、pyChecker と pyflakes はほぼ同じような結果が得られる。要するに import sys だけがチェック対象。そして、途中にエラーがあるソースに対しては、エラーが出たところまでしかチェックすることができない。エラーのチェックという点では十分といえば十分。 では pylint を使うとどうなるか。pylint だけは、import できない行があっても気にせずに処理してくれる。そして、下のようなメッセージを出す。デフォルトの状態だと、まああれこれうるさい。チェック時に実際にインポートを実行してないという点では pyflakes と同様のメリットがある。E から始まっている行だけを grep などで絞り込んでしまえば、たくさんありすぎて重要なものを見失うということもなさそう(大量のソースをやると、まあ、それもあれだが)。おもしろいのは、W: 12:test_func: Unused argument 'x' で、 x は print 文で使っているけれども、こういうのもチェック対象になるところかな。 やはり、設定ファイルを自分でちゃんと作って、制御しないと辛いところがあるかもしれないのは事実。ちなみに、もし、sonnanonaiyo が存在して、test がないのに sonnanonaiyo.test() を呼び出せば、pylint は、「E: 13: Module 'sonnanonaiyo' has no 'usoclass' member」のようにチェックしてくれる。print の結果は表示されないので、命令を実行はしないけれど、ちゃんとインポートしているファイルは追跡している。 ************* Module a C: 1: Missing docstring C: 4: Invalid name "naiyo" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) C: 6:class_a: Invalid name "class_a" (should match [A-Z_][a-zA-Z0-9]+$) C: 6:class_a: Missing docstring C: 9:class_a.test: Missing docstring R: 6:class_a: Too few public methods (1/2) C: 12:test_func: Missing docstring C: 12:test_func: Invalid name "x" (should match [a-z_][a-z0-9_]{2,30}$) W: 12:test_func: Redefining name 'x' from outer scope (line 15) W: 12:test_func: Unused argument 'x' C: 15: Invalid name "x" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) C: 16: Invalid name "y" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) W: 1: Unused import sys (以下、統計情報は長いので省略) さらにおもしろいのは、エラー時のメッセージ量。 pyChecker Processing a... SyntaxError: invalid syntax (a.py, line 10) pass ^ pyflakes a.py:10: could not compile pass ^ pylint E: 10: invalid syntax pyChecker と pyflakes はエラー箇所の文字列まで表示するのに対して、pylint は行と原因までしか分からない。 ざっくりとした印象として pyChecker は本当にモジュールをインポートしてしまうので危険な場合がある。 pyflakes はモジュールをまたがる問題に対しては無力。 pylint はうるさすぎ。きちんと設定して制御すべし。 pylint をちゃんと設定して使うのがよいのだろうけど、それもプロジェクトで本格的に使うのでなければ面倒ねと。 バランス的に、一定のレベルにある人がさくさくと使えて、最低限の要をなしてくれるプロが生産性のために pyflakes を好むのも分かる感じがする。けれども、ドキュメントストリングや変数名の規約を含めて複数の人間が一定の量のソースコードの品質を上げようとするならば pylint が好ましいとも思える。問題を分かった上で使うのであれば、pyChecker が個人で使うには、便利な面もある、けれど、やっぱり使うときに注意はすべきだし、本来デバグしているファイル以外の影響でインポート時に落ちてしまえば、そのファイルのチェックができないという弱点もある。

続きを読む

このブログでよく読まれている記事

もっと見る

総合記事ランキング

もっと見る