Paverをインストール不要な方法で使う

ごちゃっとしたプロジェクトで、setup.pyみたいなファイルだけでそのプロジェクトのビルドや配置が完結できない場合に役に立つのが paver。

でも、そのためにみんなに paver をインストールしてもらうのが気が引ける、という場合、 paver minilib というコマンドを実行すると、 paver-minilib.zip というzipファイルが生成され、Paverの基本的な機能はこれで全部カバーされる。

このファイルは23KBなので、チェックアウトしたツリーに突っ込んで配布しても良いし、いっそのこと直接バージョン管理システムに突っ込むのもありだろう。

なんだけど、 paver をインストールしないと、 paver コマンドを実行できない。なので、 minipaver とかいう名前でこんな起動スクリプトを付けると良いだろう。

#!/usr/bin/env python
import sys
sys.path.insert(0, 'paver-minilib.zip')
import paver.tasks
paver.tasks.main()

で、どうせなら、これを paver のタスク定義をしている pavement.py にくっつけてしまおう。

$ cat pavement.py
#!/usr/bin/env python
# coding: utf-8

from __future__ import with_statement # paver.easy.pushd の為
import sys

if __name__ == '__main__':
    # モジュール検索パスに paver-minilib.zip を追加.
    sys.path.insert(0, 'paver-minilib.zip')
    import paver.tasks
    paver.tasks.main()
    sys.exit()

# 以下、タスク定義
from paver.easy import *

@task
def hello():
    '''Say hello'''
    print 'hello'

$ ./pavement.py hello
---> pavement.hello
hello

で、実は今日の本題は、 sys.exit

Python のモジュールのロードは、そのモジュールを定義しているソースファイルの実行に他ならない。
むしろ、Python スクリプトの実行と思っているものが、そのソースファイルを '__main__' という名前のモジュールとしてロードしていると考えた方が良い。

さて、 pavement.py 自体を実行可能なスクリプトにした場合、 これは '__main__' としても読みこまれるが、paverの設定モジュールとして 'pavement' という名前のモジュールとしても読みこまれる。

完全名('paver.easy' みたいに、そのモジュールを含むパッケージをドットで繋げた名前)が同一の名前のモジュールであれば、その中身が実行されるのは最初のロードだけだが、複数の名前でロードされると実行も複数回になってしまう。

$ cat foo.py
import foo
print 'hello from', __name__
$ python foo.py
hello from foo
hello from __main__

通常は、pavement.py の直下には副作用のある処理は書かず関数内に書くので、ファイルが2回実行されても同じ関数が __main__ と pavement の2つのモジュール内に生成されるだけで、実害はない。

が、意図しない名前空間に勝手に関数を定義したり変数を定義したりするのは気持ち悪いので、モジュール内の変数定義や関数定義の前に if __name__ == '__main__': sys.exit() を書くことを推奨したい。

$ cat bar.py
#!/usr/bin/env python
# coding: utf-8
import sys
if __name__ == '__main__':
    import bar
    sys.exit()

print 'Hello from', __name__
$ ./bar.py 
Hello from bar
このブログに乗せているコードは引用を除き CC0 1.0 で提供します。