論理パッケージを複数のパッケージに分ける
どういうこと?
モジュールを作成している時に、一つの論理パッケージを複数のディレクトリで構成したい時があります。例えば、biokids.ccp4utilモジュールとbiokids.xdsutilモジュールを開発しているけど、これらはbiokidsパッケージである、という場合です。ディレクトリは以下のような構成になります。
lib1/ biokids/ __init__.py <-- biokidsモジュール lib2/ biokids/ __init__.py <-- 空の__init__ ccp4util.py <-- biokids.ccp4utilモジュール lib3/ biokids/ __init__.py <-- 空の__init__ xdsutil.py <-- biokids.xdsutilモジュール
一つのディレクトリで構成すればいいのですが、別々にバージョン管理をしたいとか、まだ開発途中であるとか理由はいろいろあると思います。
さて、こんな時はどのようにすれば適切に読み込めるのでしょう?
読み込むためには
まず、サーチパスについて考えてみましょう。PYTHONPATHを以下のようにすれば読み込めそうです。
PYTHONPATH=lib3:lib2:lib1
ところが、この場合、biokidsモジュールはlib3/で見つかってしまうので、lib2/は検索に行かず、biokids.ccp4utilモジュールが見つからない、となってしまいます…。この問題は以下コードをlib3/biokids/__init__.pyに記述することで解決されます。
from pkgutil import extend_path __path__ = extend_path(__path__, __name__)
- 参考 pkgutil -- http://docs.python.jp/2/library/pkgutil.html
動作としては以下のようになります。
- import biokids.ccp4utilをしたい
- biokidsモジュールを検索
- lib3で発見!
- lib3/biokids.pyを読み込む
- この時extend_path()でモジュールサーチパス__path__にlib2/biokidsおよびlib1/biokidsが追加される
- biokids.ccp4utilを検索
- biokids.__path__に従ってlib3, lib2, lib1の順に検索
- lib2/biokids/ccp4util.pyを発見
- biokids.ccp4utilを読み込み
これでディレクトリが分散されていても一つのパッケージのように振る舞うことができます。
__init__.pyは?
ところがもう一つ問題があります。import biokidsがうまく動作しません。biokidsモジュールはlib3/biokids/__init__.pyなのでlib1/biokids/__init__.pyを読み込まないのです。これを解決するには少々トリッキーな方法が必要になります。上記のextend_pathと組み合わせます。 lib3/biokids/__init__.pyつまりモジュール検索で一番最初にヒットするbiokids/__init__.pyを以下の内容にします。
import sys, pkgutil __path__ = pkgutil.extend_path(__path__, __name__) for dirname in __path__[1:]: importer = pkgutil.ImpImporter(dirname) mod = importer.find_module("").load_module("") for key in dir(mod): if key in ("__builtins__", "__doc__", "__file__", "__name__", "__package__", "__path__"): continue setattr(sys.modules[__name__], key, getattr(mod, key))
これは、extend_path()でまず__path__を拡張し、自分を含まないパス一覧(__path__[1:])から__init__.pyを読み込んで、内容を自分自身(sys.modules[__name__])に追加する、という動作をします。
もっとスマートな方法があるかもしれませんが、とりあえずこれで望む動作ができました。
コメント
コメントなどありましたらよろしくお願いします。