Python `python -m`でコマンドが動かない?`__main__`エントリーポイントの落とし穴
失礼しました!Zenn記事にもCTAを追加します。
Python python -mでコマンドが動かない?__main__エントリーポイントの落とし穴
TL;DR
-
python -m module.pathでモジュールを実行しても何も動かない場合、エントリーポイントが欠けている可能性が高い -
__main__.pyまたはif __name__ == "__main__"のどちらかが必要 - エラーも出ず終了コード0で終わる「silent failure」なので気づきにくい
- ログ出力を充実させておくと原因特定が早い
問題:正常終了するのに何も動かない
監視ツールのCLIコマンドをJenkinsで実行したところ、エラーも出ず終了コード0で正常終了するのに、処理が何も実行されていないという現象に遭遇しました。
+ python -m monitoring_sdk.core.cli metabase-check --config config.yaml --output reports
+ echo === チェック完了 ===
=== チェック完了 ===
+ ls -la reports/
total 0
drwxr-xr-x. 2 root root 40 Jan 28 02:23 .
レポートファイルも生成されず、何も起きていない。この「静かな失敗」に1時間以上悩まされました。
トラブルシューティングのプロセス
1. 最初の仮説:バッファリング問題?
最初はPythonの標準出力バッファリングを疑い、python -uフラグの追加を検討しましたが、「Python側のログ出力を強化したほうがいいのでは?」と方針転換。
各モジュールにロガーを追加し、処理の各段階でログを出すようにしました。
from monitoring_sdk.utils.logging_config import get_logger
logger = get_logger(__name__)
def metabase_check(config: str, output: str):
logger.info("metabase-checkコマンドを開始します: config=%s, output=%s", config, output)
# ... 処理 ...
logger.info("metabase-checkコマンドが完了しました")
対象は32ファイル。DevLoop Runnerを使ってissueからPRまで自動化していたので、手作業は3〜5分で済みました(処理自体は約2時間)。
2. デバッグログで見えた不自然な挙動
ログレベルをDEBUGに上げて再実行すると:
2026-01-28 09:01:37,029 - monitoring_sdk.core - DEBUG - monitoring_sdk.core パッケージを初期化しました
2026-01-28 09:01:37,066 - monitoring_sdk.monitors - DEBUG - monitoring_sdk.monitors パッケージを初期化しました
2026-01-28 09:01:37,518 - monitoring_sdk.cloud - DEBUG - monitoring_sdk.cloud パッケージを初期化しました
+ echo === チェック完了 ===
パッケージの__init__.pyは実行されている。でも、metabase_check関数内のログが一切出ない。
結論から言うと、main()関数が一度も呼ばれていませんでした。
3. 終了コードの確認
+ python -m monitoring_sdk.core.cli metabase-check --config config.yaml --output reports
+ EXIT_CODE=$?
+ echo "終了コード: $EXIT_CODE"
終了コード: 0
終了コード0。エラーではない。
--helpも試してみました:
+ python -m monitoring_sdk.core.cli --help
+ echo "ヘルプ表示完了"
ヘルプ表示完了
ヘルプの内容が何も出力されない。これは、python -mでモジュールを実行しても、エントリーポイントが正しく設定されていない状態でした。
原因:__main__エントリーポイントの欠如
cli.pyの実装を確認:
# monitoring_sdk/core/cli.py
import click
@click.group()
def main():
"""監視SDKのCLIエントリーポイント"""
pass
@main.command()
@click.option("--config", required=True)
@click.option("--output", default="reports")
def metabase_check(config: str, output: str):
# ... 実装 ...
pass
一見問題なさそうですが、ファイルの最後にif __name__ == "__main__":がありませんでした。
python -m実行時の挙動
python -m monitoring_sdk.core.cliでモジュールを実行すると:
- Pythonは
monitoring_sdk/core/cli.pyをインポートする - モジュールレベルのコード(import文、関数定義、デコレータ)は実行される
- でも
main()関数は呼ばれない - 何もせずに終了コード0で終了
パッケージの初期化ログが出ていたのは、インポート時に__init__.pyが実行されるためです。しかし、main()が呼ばれないので実際の処理は何も動きません。
Pythonのエントリーポイントについて
python -mの挙動
python -m module.pathを実行すると、Pythonは以下の順序でエントリーポイントを探します:
-
module/path/__main__.pyがあれば実行 - なければ
module/path.pyを実行(ただしif __name__ == "__main__"が必要)
重要:モジュールをインポートはするが、自動でmain()を呼んでくれるわけではない。
エントリーポイントの2つの方法
| 実行方法 | 必要なファイル/コード | 用途 |
|---|---|---|
python -m module.path |
__main__.py |
パッケージとして配布 |
python script.py |
if __name__ == "__main__" |
スクリプトとして実行 |
解決方法
方法1:__main__.pyを作成(推奨)
# monitoring_sdk/core/__main__.py
"""monitoring_sdk.core モジュールのエントリーポイント。
python -m monitoring_sdk.core.cli として実行された際に呼ばれる。
"""
from monitoring_sdk.core.cli import main
if __name__ == "__main__":
main()
方法2:cli.pyに追加
# monitoring_sdk/core/cli.py(最後に追加)
if __name__ == "__main__":
main()
両方実装する(今回の対応)
どちらの起動経路でも動くように、両方実装しました:
-
python -m monitoring_sdk.core.cli→__main__.py経由でmain()が呼ばれる -
python monitoring_sdk/core/cli.py→cli.py内のif __name__ == "__main__"でmain()が呼ばれる
修正後の動作
2026-01-28 09:49:07,417 - monitoring_sdk.monitors.metabase_monitor - INFO - カードチェックを開始します
2026-01-28 09:49:07,417 - monitoring_sdk.monitors.metabase_monitor - INFO - カードチェックが完了しました: status=OK
2026-01-28 09:49:07,417 - monitoring_sdk.monitors.metabase_monitor - INFO - Metabaseレポート生成が完了しました: file=reports/summary.md
2026-01-28 09:49:07,418 - monitoring_sdk.cli.commands.metabase_check - INFO - metabase-checkコマンドが完了しました: status=OK
期待通りの動作になりました。
学んだこと
1. silent failureは気づきにくい
エラーも出ず、終了コード0で正常終了。でも何も動いていない。こういう「静かな失敗」は、原因特定に時間がかかります。
ログ出力を充実させておくと、問題の切り分けが早くなります。 今回はデバッグログで「パッケージ初期化は動いている」「でも関数は呼ばれていない」という状況が見えました。
2. エントリーポイントの設計は基本だが見落としがち
__main__エントリーポイントの有無は基本的な確認ポイントです。でも、AI生成コードに対しては「ちゃんと実装されているだろう」という思い込みで、そもそも確認すらしていませんでした。
3. 標準的なパターンに従う重要性
Pythonには__main__.pyという標準的なパターンがあります。最初からこれに従っていれば、こんなハマり方はしなかったはずです。
4. AI駆動開発での確認ポイント
AI駆動開発では、定型的なコード生成は高速化できます。でも、エントリーポイント・起動経路の設計のような基本的な部分は、最初のテスト段階で丁寧に確認すべきです。
「動くだろう」ではなく、「本当に動くか」を確認する。そのバランス感覚が重要だと実感しました。
まとめ
-
python -mでモジュール実行する場合、__main__.pyまたはif __name__ == "__main__"が必要 - silent failureは気づきにくいので、ログ出力を充実させておく
- エントリーポイントの設計は基本だが、AI生成コードでも確認を怠らない
- 標準的なパターンに従うことで、こうした問題を未然に防げる
正直ここで詰まったのは痛かったですが、基本を確認することの重要性を改めて実感しました。
こうした判断やトラブルシューティングのプロセスを、
エンジニア向けにもう少し整理して書いています。
興味があれば、こちらにまとめています。
Discussion