読者です 読者をやめる 読者になる 読者になる

理系学生日記

おまえはいつまで学生気分なのか

忍者TOOLS

トップレベルパッケージ名が同じ複数のモジュールを作る場合 (PEP 420 あるいは package namespace)

目的

要するに、こういうことを実施したいわけ。

+ dirA
  + packageName
    - moduleA.py
+ dirB
  + packageName
    - moduleB.py

上記のような構成、つまり、別ディレクトリに packageName という共通パッケージ空間があることを前提として、

import packageName.moduleA
import packageName.moduleB

を成功させたい。どういうケースかというと、dirA 配下のファイル群と dirB 配下のファイル群は別配布だが、一方を他方のモジュールのアドオンとして提供する、というような形態を想定しているケースになる。

テスト

まず、下記のような構成を作る

% tree .
.
├── dirA
│   └── packageName
│       ├── __init__.py
│       └── moduleA.py
├── dirB
│   └── packageName
│       ├── __init__.py
│       └── moduleB.py
└── importtest.py

4 directories, 5 files

実験用スクリプト importtest.py として、以下を作成。

import sys

print(sys.version)

import packageName.moduleA
import packageName.moduleB

これを実行すると、packageName.moduleB の import でエラーとなる。ぐわーマジか。ぼくは Python newbee なわけですが、この問題に最初に気付いたとき、Python マジで??これできないの???????と驚愕した。

$ PYTHONPATH=dirA:dirB python importtest.py
3.3.2 (default, Nov  6 2013, 01:40:08) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)]
Traceback (most recent call last):
  File "importtest.py", line 6, in <module>
    import packageName.moduleB
ImportError: No module named 'packageName.moduleB'

解決策

できないわけはないらしく、調べていくと PEP 420 の Implicit Namespace Packages を利用すると楽という結論に達した。ミソは、__init__.py の削除になる。

$ find . -name __init__.py | xargs rm 

$ PYTHONPATH=dirA:dirB:$PYTHONPATH python importtest.py
3.3.2 (default, Nov  6 2013, 01:40:08) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)]

何が起こっているのか

PEP 420 に "Implicit" と書いてある通り、これは Namespace Package と呼ばれる Python の仕様を暗黙的に利用しているらしい。では Namespace Package とは何なのか。これは Python のドキュメントに記載がある。

名前空間パッケージは様々な ポーション を寄せ集めたもので、それぞれのポーションはサブパッケージを親パッケージに提供します。 ポーションファイルシステムの別々の場所にあることもあります。 ポーションは、 zip ファイルの中やネットワーク上や、それ以外のインポート時に Python が探すどこかの場所で見付かることもあります。 名前空間パッケージはファイルシステム上のオブジェクトに対応することもあるし、そうでないこともあります; それらは実際の実体の無い仮想モジュールです。

5. インポートシステム — Python 3.3.6 ドキュメント

何言っているのか分かんねぇ。
とにかくこういうときは実際に動かして見れば良い。

 % PYTHONPATH=dirA:dirB:$PYTHONPATH python
Python 3.3.2 (default, Nov  6 2013, 01:40:08) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> import packageName
>>> print(packageName.__path__)
_NamespacePath(['/Users/kiririmode/work/python/dirA/packageName', '/Users/kiririmode/work/python/dirB/packageName'])

このように、"packageName" というパッケージに関連付けられた __path__ には iterable なオブジェクトが設定されていて、その要素は個々のファイルパスになっている。モジュール検索パスとして iterable が使用されることにより、dirA 配下のモジュールであっても dirB 配下のモジュールであっても解決できるようになっているらしい。注目すべきは、PYTHONPATH に指定したただそれだけで、Python 実行系が dirA 配下の packageName と dirB 配下の packageName を見つけていることだろう。
さらに、sys.modules の中を覗くと、namespace になっていることが分かる。

>>> print(sys.modules['packageName'])                                                                                                  
<module 'packageName' (namespace)>