🐍

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でモジュールを実行すると:

  1. Pythonはmonitoring_sdk/core/cli.pyをインポートする
  2. モジュールレベルのコード(import文、関数定義、デコレータ)は実行される
  3. でもmain()関数は呼ばれない
  4. 何もせずに終了コード0で終了

パッケージの初期化ログが出ていたのは、インポート時に__init__.pyが実行されるためです。しかし、main()が呼ばれないので実際の処理は何も動きません。

Pythonのエントリーポイントについて

python -mの挙動

python -m module.pathを実行すると、Pythonは以下の順序でエントリーポイントを探します:

  1. module/path/__main__.pyがあれば実行
  2. なければ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.pycli.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生成コードでも確認を怠らない
  • 標準的なパターンに従うことで、こうした問題を未然に防げる

正直ここで詰まったのは痛かったですが、基本を確認することの重要性を改めて実感しました。


こうした判断やトラブルシューティングのプロセスを、
エンジニア向けにもう少し整理して書いています。
興味があれば、こちらにまとめています。
https://tielec.blog/

Discussion