どぶお/Pythonで遊ぼう!

論理パッケージを複数のパッケージに分ける  

どういうこと?  

モジュールを作成している時に、一つの論理パッケージを複数のディレクトリで構成したい時があります。例えば、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__)

動作としては以下のようになります。

  1. import biokids.ccp4utilをしたい
  2. biokidsモジュールを検索
  3. lib3で発見!
  4. lib3/biokids.pyを読み込む
  5. この時extend_path()でモジュールサーチパス__path__にlib2/biokidsおよびlib1/biokidsが追加される
  6. biokids.ccp4utilを検索
  7. biokids.__path__に従ってlib3, lib2, lib1の順に検索
  8. lib2/biokids/ccp4util.pyを発見
  9. 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__])に追加する、という動作をします。
もっとスマートな方法があるかもしれませんが、とりあえずこれで望む動作ができました。

コメント  

コメントなどありましたらよろしくお願いします。