Diary?

2007-11-02
Fri

(23:23)

「ええかげんにせえや Python さん」の時間がやって参りました。本日のお題は「モジュールをスクリプトとして実行した時のインポートエラー」です。まったくもってここの読者の 99% ぐらいには関係なさそうですが、まあそんなことを気にしてはいけません。あ、ちなみに Python2.5 が対象の話です。

まず、以下のようなファイルを用意し、 Python のライブラリのサーチパスの通ったところに配置してください。

if __name__ == '__main__':
  print 'foo'

実に投げやりですね。ファイル名はとりあえず foo.py とでもしておきましょうか。それでは、コマンドラインに python -m foo と打ち込んでください。画面に "foo" と表示されるはずです。このように、 Python にはモジュールをあたかも実行スクリプトのように使える機能があり、単純なフロントエンドを作る程度ならこれで十分な事もあります (逆にスクリプトがライブラリとしても使えることがわかったときにも有用)。

さてここからが本題。今度は以下のようなファイルの構成でパッケージを作ってみましょう。

foo/
   __init__.py
   bar.py

各ファイルの中身は次のようにしておきます。

#__init__.py
from bar import *
if __name__ == '__main__':
  bar()
  
#bar.py
def bar():
  print 'bar'

じゃあこれも実行してみましょうか。 python -m foo と打ち込んで、画面には……

Traceback (most recent call last):
  File "/usr/lib/python2.5/runpy.py", line 95, in run_module
    filename, loader, alter_sys)
  File "/usr/lib/python2.5/runpy.py", line 52, in _run_module_code
    mod_name, mod_fname, mod_loader)
  File "/usr/lib/python2.5/runpy.py", line 32, in _run_code
    exec code in run_globals
  File "/path/to/foo/__init__.py", line 1, in <module>
    from bar import bar
ImportError: No module named bar

ハァ!? お前は一体何を言っているんだ。テメエ、そこに bar.py あるじゃねえか。どこに目ぇ付けてんだよ、ケツか? これじゃ実行できねえじゃねええかああああ!!

えーと、これがどういうことかっつーとだな、簡単に書けば普通に foo パッケージをモジュールとして使う場合には /path/to/foo 以下のファイルが見える状態で実行されるけど、スクリプトとして使う場合にはカレントディレクトリに __init__.py をコピーしてきてそれを実行した場合と同じ状態になるってことだ。詳細は runpy.py, pkgutil.py あたりのソース読むしかないんだけど、実際にやってることはモジュール名からファイル名を探り当てて (モジュール名とファイル/ディレクトリ名が一致する仕様だから出来る技)、それを読み込んでオンデマンドで compile して exec という代物だ。なので、モジュールとして使っていた場合は __init__.py から見えていた bar.py は実行時には見えなくなっているわけだ。ちなみに /path/to/foo に移動して実行すると、何の問題もなく実行できる。これは実行時にはカレントディレクトリに bar.py があるので、そこからインポートしているってわけ。

このままだとパッケージをスクリプトとして使えなくなってしまうので、次のように __init__.py の記述を変えよう。

from foo.bar import *
if __name__ == '__main__':
  import sys
  sys.exit(main())

考えてみれば当たり前だけど、絶対パスで指定すれば問題は起こらんよな。

それにしても pkgutil や imp はドキュメントが古いままなのか間違ってるのか、本家にあるライブラリリファレンスが当てにならない (特に pkgutil が酷い)。まあ、滅多に使わないモジュールではあるんだけどよ。

追記: 最初は実行時のみ絶対パス指定してたけど (サーチパスの順序によってはおかしなことになりそうだったから)、よくよく考えたらそんなこたぁまず起こらねえよな。なので今の形式に修正。

Creative Commons
この怪文書はクリエイティブ・コモンズ・ライセンスの元でライセンスされています。引用した文章など Kuwata Chikara に著作権のないものについては、それらの著作権保持者に帰属します。