GoogleAppEngine の Xcode プロジェクトを PyChecker でエラーチェックする

私の使っている MacOSX 10.6 はデフォルトで python2.6 を使用するため、GoogleAppEngine の python2.5 準拠のプロジェクトで問題なく動作させるために設定した項目をいくつか書きます。

  • PyChecker について

Google Python スタイルガイド で以下のように記述されています。

定義:
Pychecker は Pythonソースコード内のバグを発見するためのツールです。 C や C++ のような、非動的言語コンパイラが引き起こすような問題を発見します。 lint とよく似ています。 Python の動的な特性により、いくつかの警告は正確ではない可能性があります - とはいえ、不正確な警告はほとんどありません。
利点:
タイプミス、未初期化変数の利用などの単純ミスを発見することができます。
欠点:
pychecker は完璧ではありません。利用するために、a) コードを書き b) 警告を無効にし c) 改良するか d) 無視する 等が必要になることがあります。

PyChecker はホームページからダウンロードできます。

  • PyChecker の使用法

コマンドラインで PyChecker を使用するには、以下の二通りの方法があります。

# インストールされている PyChecker を実行する場合
pychecker [options] file1.py file2.py ...

# ソースコード (pychecker-0.8.19/pychecker/checker.py) から PyChecker を実行する場合
python checker.py [options] file1.py file2.py ...

今回は、好きな python のバージョンから実行できるように、ソースコードから実行する方法を用います。
ちなみに、PyChecker はチェックするファイルを file1.py file2.py ... というように、まとめて指定する必要があるようです。

  • PyChecker のオプションと例

コマンドラインで pychecker の -h オプションを用いると、以下のオプションが閲覧できます。

Usage for: checker.py [options] PACKAGE ...

    PACKAGEs can be a python package, module or filename

Long options can be preceded with no- to turn off (e.g., no-namedargs)

Category
  Options:           Change warning for ... [default value]

Major Options:
      --only         only warn about files passed on the command line [off]
  -e, --level        the maximum error level of warnings to be displayed
  -#, --limit        the maximum number of warnings to be displayed [10]
  -F, --config       specify .pycheckrc file to use
      --quixote      support Quixote's PTL modules
      --evil         list of evil C extensions that crash the interpreter [[]]
      --keepgoing    ignore import errors [off]

Error Control:
  -i, --import       unused imports [on]
  -k, --pkgimport    unused imports from __init__.py [on]
  -M, --reimportself module imports itself [on]
  -X, --reimport     reimporting a module [on]
  -x, --miximport    module does import and from ... import [on]
  -l, --local        unused local variables, except tuples [on]
  -t, --tuple        all unused local variables, including tuples [off]
  -9, --members      all unused class data members [off]
  -v, --var          all unused module variables [off]
  -p, --privatevar   unused private module variables [on]
  -g, --allglobals   report each occurrence of global warnings [off]
  -n, --namedargs    functions called with named arguments (like keywords) [off]
  -a, --initattr     Attributes (members) must be defined in __init__() [off]
  -I, --initsubclass Subclass.__init__() not defined [off]
  -u, --callinit     Baseclass.__init__() not called [on]
  -0, --abstract     Subclass needs to override methods that only throw exceptions [on]
  -N, --initreturn   Return None from __init__() [on]
  -8, --unreachable  unreachable code [off]
  -2, --constCond    a constant is used in a conditional statement [on]
  -1, --constant1    1 is used in a conditional statement (if 1: or while 1:) [off]
      --stringiter   check if iterating over a string [on]
      --stringfind   check improper use of string.find() [on]
  -A, --callattr     Calling data members as functions [off]
  -y, --classattr    class attribute does not exist [on]
  -S, --self         First argument to methods [self]
      --classmethodargs First argument to classmethods [['cls', 'klass']]
  -T, --argsused     unused method/function arguments [on]
  -z, --varargsused  unused method/function variable arguments [on]
  -G, --selfused     ignore if self is unused in methods [off]
  -o, --override     check if overridden methods have the same signature [on]
      --special      check if __special__ methods exist and have the correct signature [on]
  -U, --reuseattr    check if function/class/method names are reused [on]
  -Y, --positive     check if using unary positive (+) which is usually meaningless [on]
  -j, --moddefvalue  check if modify (call method) on a parameter that has a default value [on]
      --changetypes  check if variables are set to different types [off]
      --unpack       check if unpacking a non-sequence [on]
      --unpacklen    check if unpacking sequence with the wrong length [on]
      --badexcept    check if raising or catching bad exceptions [on]
  -4, --noeffect     check if statement appears to have no effect [on]
      --modulo1      check if using (expr % 1), it has no effect on integers and strings [on]
      --isliteral    check if using (expr is const-literal), doesn't always work on integers and strings [on]
      --constattr    check if a constant string is passed to getattr()/setattr() [on]

Possible Errors:
  -r, --returnvalues check consistent return values [on]
  -C, --implicitreturns check if using implict and explicit return values [on]
  -O, --objattrs     check that attributes of objects exist [on]
  -7, --slots        various warnings about incorrect usage of __slots__ [on]
  -3, --properties   using properties with classic classes [on]
      --emptyslots   check if __slots__ is empty [on]
  -D, --intdivide    check if using integer division [on]
  -w, --shadow       check if local variable shadows a global [on]
  -s, --shadowbuiltin check if a variable shadows a builtin [on]

Security:
      --input        check if input() is used [on]
  -6, --exec         check if the exec statement is used [off]

Suppressions:
  -q, --stdlib       ignore warnings from files under standard library [off]
  -b, --blacklist    ignore warnings from the list of modules
			 [['Tkinter', 'wxPython', 'gtk', 'GTK', 'GDK']]
  -Z, --varlist      ignore global variables not used if name is one of these values
			 [['__version__', '__warningregistry__', '__all__', '__credits__', '__test__', '__author__', '__email__', '__revision__', '__id__', '__copyright__', '__license__', '__date__']]
  -E, --unusednames  ignore unused locals/arguments if name is one of these values
			 [['_', 'empty', 'unused', 'dummy']]
      --missingattrs ignore missing class attributes if name is one of these values
			 [[]]
      --deprecated   ignore use of deprecated modules/functions [on]

Complexity:
  -L, --maxlines     maximum lines in a function [200]
  -B, --maxbranches  maximum branches in a function [50]
  -R, --maxreturns   maximum returns in a function [10]
  -J, --maxargs      maximum # of arguments to a function [10]
  -K, --maxlocals    maximum # of locals in a function [40]
  -5, --maxrefs      maximum # of identifier references (Law of Demeter) [5]
  -m, --moduledoc    no module doc strings [off]
  -c, --classdoc     no class doc strings [off]
  -f, --funcdoc      no function/method doc strings [off]

Debug:
      --rcfile       print a .pycheckrc file generated from command line args
  -P, --printparse   print internal checker parse structures [off]
  -d, --debug        turn on debugging for checker [off]
      --findevil     print each class object to find one that crashes [off]
  -Q, --quiet        turn off all output except warnings [off]
  -V, --version      print the version of PyChecker and exit

例えば、--stdlib オプションを有効 [on] にしたい場合、--stdlib とだけ書きます。
また、--deprecated オプションを無効 [off] にしたい場合、--no-deprecated と書きます。


私がある AppEngine プロジェクトで使用しているオプションの組み合わせは以下の通りです。
実行時はコメントを削除してください。

python2.5 checker.py \
    --stdlib \    # --stdlib [on]
    --blacklist null,BeautifulSoup,oauth,sgmllib,markupbase \    # --blacklist ['null', 'BeautifulSoup', 'oauth', 'sgmllib', 'markupbase']
    --no-override \    # --override [off]
    --unusednames cls \    # --unusednames ['cls']
    $FILES    # file1.py file2.py ...
# オプションの説明
--stdlib    # Python 標準ライブラリ内のエラーを無視する
--blacklist    # 外部・オープンソースモジュールのエラーを無視する
--no-override    # 継承したクラスでメソッドのオーバーライドを許可する
--unusednames    # 引数が未使用でもよいもの(クラスメソッドの cls 等)

全てのオプションを指定した後ろに --rcfile を追加すると、オプション変更後の .pycheckrc ファイルの内容が出力されます。

  • セットアップ for Python2.5

前述したように、YAML, WebOb, Django 等のサードパーティライブラリはデフォルトの Python2.6 (/Library/Python/2.6/site-packages) に対してインストールされています。
これを Python2.5 (/Library/Python/2.5/site-packages) からでも参照できるように、シンボリックリンクを作成します。

# YAML のシンボリックリンクを作成
ln -s /Library/Python/2.6/site-packages/yaml/ /Library/Python/2.5/site-packages/yaml

# Django のシンボリックリンクを作成
ln -s /Library/Python/2.6/site-packages/django/ /Library/Python/2.5/site-packages/django


WebObWebOb-0.9-py2.6.egg となっていて、Python2.6 専用のものがインストールされてしまっているため、新たに Python2.5 に対してインストールさせます。
AppEngine では WebOb 0.9 なのですが、なかなか見つけられなかったので最新バージョンの 1.0.6 で代用しました。 エラーチェック用なので、API が変更されていなければ問題ないはず・・・

python2.5 setup.py install    # WebOb-1.0.6-py2.5.egg がインストールされる

追記)SDK コードに WebOb 0.9 が入っていました。


GoogleAppEngine の google モジュールについても、 Python2.6 にしかシンボリックリンクが作成されていなかったため、このシンボリックリンクを Python2.5 にリンクさせました。

ln -s /Library/Python/2.6/site-packages/google/ /Library/Python/2.5/site-packages/google

ちなみに MacOSX の場合、google/ 本体は /Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google にあります。

これで PyChecker が GoogleAppEngine ライブラリに対して出す ImportError を解決できるでしょう。

  • Xcode のターゲット設定

PyChecker にソースコードを直接 検証させて、何か問題が生じたら嫌なので、Xcode のプロダクトディレクトリにコピーしたものをエラーチェックさせます。

PyChecker を実行するシェルスクリプトは以下のようになります。

cd $BUILT_PRODUCTS_DIR    # プロダクトディレクトリに移動

FILES=""
for f in *.py
do
    FILES="$FILES $f"    # チェックするファイル名をスペース区切りで結合
done

# PyChecker ソースコードのパス
PYCHECKER=/Users/User/Documents/Web_Project/Python-Libraries/pychecker-0.8.19/pychecker/checker.py

python2.5 $PYCHECKER \
    --stdlib \
    --blacklist null,BeautifulSoup,oauth,sgmllib,markupbase \
    --no-override \
    --unusednames cls \
    $FILES

exit 0

python2.5 でエラーチェックさせることで、md5 等の deprecation 警告に悩まされることもなくなります。
外部ライブラリでエラーが出た場合、--blacklist にファイル名を追加してください。

  • 追記

Pychecker を OS に再インストールしたりすると、google.appengine.ext.webapp.__init__ の警告が消えてくれることもある。 ソースコードから実行しているのに不思議。 OS 側の stdlib 設定が書き変わるから?
Google App Engine SDK をインストールし直して変になった時は、試してみてください。

# pychecker-0.8.19
sudo python2.5 setup.py install
  • 追記

一度 OS 標準の Python(2.6) で PyChecker を実行した後に、再び Python2.5 で実行すると google ライブラリのエラーチェックを無視することができました。
PYTHONPATH (sys.path で確認できる) の追加(以下)は効果ありませんでした。

export PYTHONPATH=$PYTHONPATH:/Library/Python/2.5/site-packages