いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Pythonメモ-27 (名前空間パッケージ)(PEP420, Implicit Namespace Packages)

概要

Pythonのパッケージの仕組みは、シンプルな考え方でディレクトリ内に __init__.py があればパッケージとして認識してくれます。

なのですが、同じパッケージ名で複数のモジュール構造を作ろうとするとエラーになります。

サンプル

以下のような構造(ライブラリが2つあってどっちもutilsパッケージ持ってる)をつくったとして

│  main.py
│
├─lib1
│  └─utils
│          __init__.py
│          strutils.py
│
└─lib2
    └─utils
            __init__.py
            bytesutils.py

strutilsbytesutils.pyは以下のような感じとします。

def to_b(s: str) -> bytes:
    if not isinstance(s, str):
        raise ValueError('not str')
    return s.encode('utf-8')
def to_s(b: bytes) -> str:
    if not isinstance(b, bytes):
        raise ValueError('not bytes')
    return b.decode('utf-8')

main.pyは以下ような感じとします。

import sys

sys.dont_write_bytecode = True
sys.path.extend('lib1 lib2'.split())

from utils.strutils import to_b
from utils.bytesutils import to_s

s = 'hello world'
b = to_b(s)

print(f'{type(b)}')

s2 = to_s(b)
print(f'{type(s2)}')

実行すると以下のエラーとなります。

$ python main.py
Traceback (most recent call last):
  File "main.py", line 7, in <module>
    from utils.bytesutils import to_s
ModuleNotFoundError: No module named 'utils.bytesutils'

で、これを可能にするのが PEP420 -- Implicit Namespace Packagesとなります。

www.python.org

python 3.3 から追加された機能となります。

上記ページは英語でいろいろ書いてあるのですが

大事なのは specification の部分のここです。

以下、上記ページから引用。

Namespace packages cannot contain an __init__.py.

名前空間パッケージにしたい場合は、__init__.pyを含めてはいけないってことですね。

てことで、先ほどエラーになった構造から __init__.pyを削除します。

│  main.py
│
├─lib1
│  └─utils
│          strutils.py
│
└─lib2
    └─utils
            bytesutils.py

こうなります。で、再度実行してみる。

$ python main.py
<class 'bytes'>
<class 'str'>

今度は、ちゃんと動きました。

ちなみに、上記PEPにモジュールの探索パスもちゃんと書いてあります。

以下のようになるとのこと。上記ページより引用。

・If /foo/__init__.py is found, a regular package is imported and returned.

・If not, but /foo.{py,pyc,so,pyd} is found, a module is imported and returned. The exact list of extension varies by platform and whether the -O flag is specified. The list here is representative.

・If not, but /foo is found and is a directory, it is recorded and the scan continues with the next directory in the parent path.

・Otherwise the scan continues with the next directory in the parent path.

まあ、普段こんなパッケージ構成にはあまりしないと思いますが、最悪必要となった場合に同じパッケージ名でもイケルってのは知っておくと得かも。


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。