nikkie-ftnextの日記

イベントレポートや読書メモを発信

mypkg/fabulous.py を mypkg/fabulous/__init__.py に変えた時、python -m mypkg.fabulous で動かし続けるには __main__.py も必要でした

はじめに

七尾百合子さん、お誕生日 309日目 おめでとうございます! nikkieです。

プライベートでの Python パッケージ開発からの学びです。

目次

結論:パッケージをpython -mに渡すには__main__.pyが要る

以下のようにパッケージを実行したい時

python -m mypkg.awesome

python -m mypkg.fabulous

以下のような構造が必要です

mypkg/
├── __init__.py
├── awesome.py  # python -m mypkg.awesome で(__main__を)実行
└── fabulous/
    ├── __init__.py
    └── __main__.py  # python -m mypkg.fabulous で実行

python -mに渡すものがモジュール(.pyファイル)かパッケージ(__init__.pyを置いたディレクトリ)かで、要求されるものが異なることを学びました

Python 3.13.0 で動作確認したリポジトリです

全て書いてあったpython -mのドキュメント

https://docs.python.org/ja/3/using/cmdline.html#cmdoption-m

-m <module-name>
sys.path から指定されたモジュール名のモジュールを探し、その内容を __main__ モジュールとして実行します。

引数は module 名なので、拡張子 (.py) を含めてはいけません。(略)

パッケージ名 (名前空間パッケージも含む) でも構いません。通常のモジュールの代わりにパッケージ名が与えられた場合、インタプリタ<pkg>.__main__ を main モジュールとして実行します。

経験した事象:モジュールをパッケージにしたらpython -mで動かない

元々python -m recent_state_summarizer.fetchと実行できていました。

recent_state_summarizer/
├── __init__.py
└── fetch.py

fetch.pyには、__main__トップレベル環境)となる場合のコードが書かれていました。
ref: https://docs.python.org/ja/3/library/__main__.html#what-is-the-top-level-code-environment

if __name__ == "__main__":
    cli()

このfetch.pyが肥大化してきたので、fetch パッケージを導入しました。
fetch.pyfetch/__init__.pyとするだけで、既存の import を壊さずにfetchディレクトリが導入できます

recent_state_summarizer/
├── __init__.py
└── fetch/
    └── __init__.py

fetch/__init__.py__main__となる場合のコードを残していたのですが、python -m recent_state_summarizer.fetchと実行できなくなりました。

'recent_state_summarizer.fetch' is a package and cannot be directly executed

この理由ですが、python -mのドキュメントにあるように、fetch/__main__.pyが必要なためと学びました

recent_state_summarizer/
├── __init__.py
└── fetch/
    ├── __init__.py
+    └── __main__.py

__main__.pyにはif文不要

https://docs.python.org/ja/3/library/__main__.html#id1

The content of __main__.py typically isn't fenced with an if __name__ == '__main__' block.
Instead, those files are kept short and import functions to execute from other modules.

__main__.pyにはif __name__ == '__main__'は不要。
他のモジュールから関数を import して短く保つ

つまり以下だけでよいということです。
if文を書かなくても伝わります__main__とならない場合がありません)

# import文の例示は省略
cli()

終わりに

python -mにパッケージ名を指定した時、__main__.pyが実行されます。
python -mで実行していたモジュールをパッケージに変えた時、__main__.pyも用意しないとpython -mでの実行だけ壊してしまいます(俺みたいになるな!)。
__main__.pyはトップレベル環境__main__として実行されるものなので、if __name__ == '__main__'は不要で短く済ませます。

過去にpython -msys.pathの関係を書きましたが、__main__.pyとの関係で本記事が爆誕しました