ネストされたモジュール

Pythonのスクリプトやプログラムがかなり大きくなることをこれまでに見てきました。複雑さを管理し、コードをクリーンで読みやすく保つために、モジュールやパッケージを使ってプログラムを構造化することができます。Pythonでは、モジュールは単にPythonコードを含むファイルです。モジュールは他のモジュールをインポートすることができ、その結果、ネストされたモジュールが生じることがあります。一方、パッケージは関連するモジュールをディレクトリ階層で整理する方法です。
例えば、ゲームを開発しており、「sound(音)」、「level(レベル)」、「character(キャラクター)」などの異なる機能があり、それぞれが独自のモジュールに分けられているとします。その上で、「sound」モジュール自体にも「effects(エフェクト)」、「filters(フィルター)」、「echo(エコー)」などのさまざまなコンポーネントがあります。このような場合、ネストされたモジュールを作成することができます。具体的な作成方法を見てみましょう。
まず、パッケージ用のディレクトリ(フォルダ)を作成します。例えば、このディレクトリを「game」と名付けます。
game/                     # ゲームパッケージのディレクトリ
「game」ディレクトリ内に「sound」ディレクトリと空の__init__.pyファイルを作成します。__init__.pyファイルは、Pythonにこのディレクトリがパッケージを含んでいることを知らせるために必要です。
game/
    sound/                # 'sound'モジュールのサブディレクトリ
        __init__.py       # Pythonに'sound'をパッケージとして認識させる
次に、「sound」ディレクトリ内に「effects」、「filters」、「echo」のためのPythonファイルをそれぞれ作成します。これらのファイルは、「sound」モジュールのサブモジュールを表します。
game/
    sound/
        __init__.py
        effects.py        # 'effects'サブモジュール
        filters.py        # 'filters'サブモジュール
        echo.py           # 'echo'サブモジュール
例えば、effects.pyは次のようになります。
def echo_filter(soundwave):
    return soundwave + '...'   # サウンドウェーブにエコーを追加

def distort_filter(soundwave):
    return soundwave[::-1]     # サウンドウェーブを反転してディストーション効果
gameパッケージ内の別のモジュールでこれらの関数を使用したい場合、インポートすることができます。パッケージ内を移動するためにドット(.)を使用します。仮想的なgameplay.pyファイルでの例を見てみましょう。
from game.sound.effects import echo_filter, distort_filter   # 関数をインポート

soundwave = 'pew pew'
echoed = echo_filter(soundwave)
distorted = distort_filter(soundwave)

print(echoed, type(echoed))         # 'pew pew...', <class 'str'> を出力
print(distorted, type(distorted))   # 'wep wep', <class 'str'> を出力
このように、Pythonコードをモジュールやパッケージに整理することで、コードをクリーンで読みやすく保ち、保守や開発が容易になります。

Pythonパッケージにおける__init__.pyの役割

Pythonでは、__init__.pyファイルはパッケージとして使用するディレクトリに対して特別な役割を果たします。従来は、ディレクトリをモジュールを含むパッケージとしてPythonに認識させるために、__init__.pyファイルが必要でした。しかし、Python 3.3以降では、暗黙のネームスペースパッケージの導入により、必ずしも必要ではなくなりました。
それでも、__init__.pyファイルは廃止されたわけではなく、特定の状況で有用性を維持しています。例えば、パッケージの初期化コードを実行したり、__all__変数でインポートを制御したり、便利なインポートを定義したりすることができます。次のディレクトリ構造を考えてみましょう。
game/
    __init__.py
    sound/
        __init__.py
        effects.py
        filters.py
        echo.py
game/sound/__init__.pyには次のように書くことができます。
from .effects import echo_filter
from .filters import distort_filter
from .echo import echo_sound
ここでは、サブモジュールを__init__.pyファイルでインポートしています。これにより、effectsfiltersechoのサブモジュールをsoundをインポートすると直接アクセスできます。プログラムの別の部分で次のように使用できます。
from game.sound import echo_filter, distort_filter

soundwave = 'pew pew'
echoed = echo_filter(soundwave)
distorted = distort_filter(soundwave)
この例では、__init__.pyがさまざまなサブモジュールを統合し、機能をインポートするための簡潔なインターフェースを提供する上で重要な役割を果たしています。Pythonの新しいバージョンでは__init__.pyの存在は必須ではありませんが、このように使用することでパッケージをより使いやすく管理しやすくすることができます。
 
To check your solution you need to sign in
Sign in to continue