Pythonにおけるパッケージの定義と初期化の仕組み

パッケージの基本構造

Pythonでは、コードの組織単位としてモジュールとパッケージが用いられます。モジュールは単一の.pyファイルですが、複数のモジュールやサブパッケージをまとめるために「パッケージ」を使用します。ディレクトリ内に__init__.pyファイルが存在する場合、そのディレクトリはPythonによってパッケージとして認識されます。

__init__.pyは空ファイルでも構いませんし、初期化コードを含んでいても構いません。このファイルの有無が、ディレクトリがパッケージとして扱われるかどうかの分岐点です。

パッケージのインポートと初期化動作

パッケージが初めてインポートされた際、対応する__init__.py内のコードが実行されます。ただし、一度読み込まれたパッケージはキャッシュされるため、再度インポートしても__init__.pyの内容は再実行されません。

したがって、__init__.pyには副作用のある実行文ではなく、関数やクラスの定義、あるいは他のモジュールのインポート処理を記述することが推奨されます。

インポート時の名前解決順序

以下のようなインポート文を考えます:

from mypackage import something

このとき、Pythonは以下の順でsomethingを探します:

  1. mypackage/__init__.py内で定義された変数・関数・クラス
  2. mypackage/something/というサブディレクトリ(サブパッケージ)が存在するか
  3. mypackage/something.pyというモジュールファイルが存在するか

どれにも該当しない場合はImportErrorが発生します。

具体例:mypackageの構成

以下のような構成のパッケージを想定します:

mypackage/
├── __init__.py
├── core.py
└── utils.py

core.pyの内容:

def process_data():
    print("Processing data in core module")

utils.pyの内容:

def helper_func():
    print("Helper function from utils")

この状態でインタラクティブシェルから操作すると:

>>> import mypackage
>>> mypackage.core
AttributeError: module 'mypackage' has no attribute 'core'

これは、__init__.pyが空のため、coreモジュールがmypackage名前空間に読み込まれていないためです。

自動インポートの実現方法

上記のエラーを回避し、import mypackageだけでmypackage.coremypackage.utilsを使えるようにするには、__init__.pyに次のように記述します:

# mypackage/__init__.py
from . import core
from . import utils
print("mypackage initialized")

これにより、以下のように利用可能になります:

>>> import mypackage
mypackage initialized
>>> mypackage.core.process_data()
Processing data in core module

大規模なパッケージ構成での活用

以下のような音声処理ライブラリを想定します:

audio_toolkit/
├── __init__.py
├── engine/
│   ├── __init__.py
│   └── processor.py
├── io/
│   ├── __init__.py
│   └── wav_handler.py
└── effects/
    ├── __init__.py
    └── reverb.py

トップレベルのaudio_toolkit/__init__.pyに以下を記述することで、ユーザーが簡単に機能にアクセスできるようになります:

"""Audio processing toolkit main package"""
from . import engine
from . import io
from . import effects

# よく使う関数をトップ名前空間に展開
from .engine.processor import process_audio
from .io.wav_handler import load_wav, save_wav

これにより、利用者は次のように簡潔に使用できます:

>>> import audio_toolkit as atk
>>> data = atk.load_wav("input.wav")
>>> processed = atk.process_audio(data)
>>> atk.save_wav("output.wav", processed)

相対インポートの活用

サブパッケージ内部では、相対インポートを使って他モジュールを参照できます。例えばaudio_toolkit/effects/__init__.py内で:

from ..engine import processor
from ..io import wav_handler

この記法により、親パッケージ内のモジュールを明確に参照できます。ドット(.)は現在のパッケージを、..は一つ上の階層を意味します。

タグ: Python package __init__.py import relative-import

5月20日 23:16 投稿