Python講座 / パッケージと安全な実行入口を作る

パッケージは関連するモジュールを階層でまとめる仕組みです。実行処理をmain()と明示的な入口へ置くと、別のコードからimportしただけでアプリケーションが自動実行されることを防げます。

通常パッケージの構成を作る

この入門例では__init__.pyを持つ通常パッケージを使います。名前空間パッケージにはこのファイルを省略できる構成もありますが、ここでは明示的な構造を確認します。コマンドはexpense_appの中ではなく、その親のprojectから実行します。

project/
  expense_app/
    __init__.py
    math_tools.py
    __main__.py

モジュール名とインストール可能なパッケージは、importでモジュールを読み込み、コードを分割するで扱う規則とつながります。

全ファイルを保存し、パッケージを実行する

expense_app/__init__.pyです。

"""Expense calculation package."""

expense_app/math_tools.pyです。

def total(amounts):
    return sum(amounts)

expense_app/__main__.pyです。

from .math_tools import total

def main():
    print(total([3500, 8000, 2500]))

if __name__ == "__main__":
    main()

projectで次を実行します。

python -m expense_app

結果は14000です。-mはモジュール探索で対象を見つけ、パッケージの文脈を与えます。python expense_app/__main__.pyという直接のファイル実行は別で、この例の相対importに必要な文脈を同じようには与えません。

__name__で実行と再利用を分ける

実行対象には__main__という名前が与えられ、importしたモジュールにはパッケージ名・モジュール名が与えられます。if __name__ == "__main__"は、その違いでmain()を呼ぶか判断します。関数定義は最上位に置き、計算やファイル変更は呼び出しまで待たせます。

データファイルとモジュールは探し方が違います。相対的なデータパスは作業フォルダーに依存するため、配布するアプリでは明示的なパス引数やパッケージ内リソースを扱う方法が必要な場合があります。常にソースの場所から起動されるとは限りません。

importだけの動作を別に確認する

projectで実行します。

python -c "import expense_app.__main__; print('imported')"

表示はimportedだけです。合計も出る場合は、最上位でまだ実行している文を探します。アプリとしての実行と、安全に再利用するimportを別々にテストすると、出力確認だけでは見落とす問題を検出できます。

パッケージ名を変えたら、importパスと実行コマンドも合わせて変えます。ソースの整理と、配布用メタデータ・ビルド・導入検証は別の作業です。配布に必要な追加工程はパッケージ化で扱います。

パッケージ外側のプロジェクトルートから python -m package.module で実行するとパッケージの文脈が保たれます。相対importを使う内部ファイルを直接実行すると、親パッケージが不明なためImportErrorになることがあります。コードを変えず、実行場所とコマンドだけを変えて比較します。

関連する公式リファレンス