python -m json.tool を実行したときの動きを確認してみた。

2023.03.22

Pythonを起動するとき、-mをつけて起動するというやり方をする時があります。 この時の動作がどうなっているのかよくわかっていなかったので調べてみました。

$ echo '{"aaa": 123}' | python -m json.tool
{
    "aaa": 123
}

こんな感じで動くやつですね。 今回はこのjson.toolを題材としています。

なぜ動くのか

そもそもpython -m xxxはどういう意味なのかというと、 公式ドキュメントを見ると以下のように書かれています。

sys.path から指定されたモジュール名のモジュールを探し、その内容を main モジュールとして実行します。

わかってから読むと、その通りなんですが、 わからない状態で読むとよくわからない文章が書いてあります。

json.toolはどこにある?

__file__でモジュールのファイルパスを得ることができます。 普通にPythonの中でjson.toolをimportして調べてみます。

$ python -c 'import json.tool; print(json.tool.__file__)'
/opt/homebrew/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/json/tool.py

ファイルの場所がわかりました。 実際にそのファイルが存在することも確認できます。

python -m json.toolでなぜいきなりこれが起動するか

sys.path から指定されたモジュール名のモジュールを探し、その内容を main モジュールとして実行します。

sys.pathからモジュール名を探すとありますので、sys.pathの中身を調べてみます。

$ python -c 'import sys; print("\n".join(sys.path))'

/opt/homebrew/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python310.zip
/opt/homebrew/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10
/opt/homebrew/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/lib-dynload
/opt/homebrew/lib/python3.10/site-packages

1行目が空文字になっているのは、カレントディレクトリを意味しています。 また2行目のzipファイルは、実際にこの場所には存在していませんでした。

これらの場所から、json/tool.pyを探して行きます。 結果として /opt/homebrew/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10 の下にjson/tool.pyが存在しましたのでこのファイルが実行されていたことになります。

これは普通のPythonファイルです。 こんな見慣れたコードが書かれています。

def main():
    #(略)

if __name__ == '__main__':
    try:
        main()

一応以下のように適当なprintを入れると実際に出力が見られました。

if __name__ == '__main__':
    try:
        print("呼ばれたよ")
        main()
$ echo '{"aaa": 123}' | python -m json.tool
呼ばれたよ
{
    "aaa": 123
}

間違いなくこのファイルが呼ばれていることが確認できました。

フルパス指定で動かすとどうなるか

普通のPythonファイルがどこにあるかわかったので、このファイルを直接指定して実行してみます。

$ echo '{"aaa": 123}' | python /opt/homebrew/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/json/tool.py
呼ばれたよ
{
    "aaa": 123
}

普通に動きました。 追加したprint文は戻しておきましょう。

ちなみに今回は特に関係ないですが、ファイルを直接指定するのと-mを指定することの違いとして、

  • 直接指定: 実行したPythonファイルが存在するディレクトリがsys.pathに追加される
  • -m指定: カレントディレクトリがsys.pathに追加される

のような違いがあるようです。

まとめ

-mをつけるとsys.pathからモジュールを探して実行する、 というドキュメントに書かれていることをそのまま検証しただけですが、 実際に手元で確認することでより納得して理解することができました。

蛇足ですが、-mって何の略なのか?という話ですが、 ドキュメントに<module-name>と書いてありますので、modulemのようです。 __main__として実行するのでmainmかと思っていました。

誰かの参考になれば幸いです。