-mオプションでの動作
通常のスクリプトでの実行との主な違いとしては、以下のようにsys.argv
とsys.path
の先頭に追加されるパスが異なるという点のようです。
sys.argv
- スクリプト実行
-
カレントディレクトリから実行されるファイルまでの相対パス
が追加される
-
- -mオプション付きで実行
-
実行されるファイルの絶対パス
が追加される
-
sys.path
- スクリプト実行
-
実行されるファイルが存在するディレクトリの絶対パス
が追加される。
-
- -mオプション付きで実行
-
カレントディレクトリの絶対パス
が追加される。
-
公式ドキュメントでの説明
以降は細かい検証等になります。上記の説明よりも詳細が知りたいという方向けです。
まずは公式ドキュメントを参照します。
調べる際、最初は公式ドキュメントを見る癖付けをしなきゃなと最近感じています。
公式ドキュメントの中で動作を説明しているのは以下の文章の部分になります。これを解読していきましょう。
sys.path から指定されたモジュール名のモジュールを探し、その内容を main モジュールとして実行します。
このオプションが指定された場合、 sys.argv の最初の要素はモジュールファイルのフルパスになります (モジュールファイルを検索している間、最初の要素は "-m" に設定されます)。 -c オプションと同様に、カレントディレクトリが sys.path の先頭に追加されます。
前半部分
sys.path から指定されたモジュール名のモジュールを探し、その内容を main モジュールとして実行します。
sys.path
sys.path
はモジュール検索パスと呼ばれ、外部モジュール(pandasやnumpyなど)をインポートする際のパス解決のリストとして使われます。
実際にsys.path
を出力して見てみましょう。
$ python
>>> import sys
>>> sys.path
['', '/Users/ryku/opt/anaconda3/lib/python38.zip', '/Users/ryku/opt/anaconda3/lib/python3.8', '/Users/ryku/opt/anaconda3/lib/python3.8/lib-dynload', '/Users/ryku/opt/anaconda3/lib/python3.8/site-packages', '/Users/ryku/opt/anaconda3/lib/python3.8/site-packages/aeosa']
これがモジュール検索パスです。
sys.path から指定されたモジュール名のモジュールを探し
ということなので、例えばpython -m pandas
と実行した場合、まずは''
の中(カレントディレクトリ)を見にいきます。その中にpandas
が無かったら次は/Users/ryku/opt/anaconda3/lib/python38.zip
ディレクトリの中にあるかどうかを見に行く...というような流れになります。
最初の空文字''はカレントディレクトリを示しています。
python -c codeおよびpython(REPL) コマンド ライン: 先頭に空の文字列を追加します。これは、現在の作業ディレクトリを意味します。
mainモジュール
pythonのスクリプトは、~.py
というファイルに保存された時点でモジュールになります。モジュールはpythonのインタラクティブモードからimportしたり、他のモジュールからimportしたりすることができます。
mainモジュールとは、実行のトップレベルや電卓モードでアクセスできる変数の集まり、と定義されています。
電卓モードはインタラクティブモードと読み替えて良さそうです。
モジュール実行例
例として以下のようなpythonファイルを作成します。
print(f"file: {__name__}")
__name__
という変数を使用して、そのファイルのモジュール名を取得できます。
このモジュールを2種類の方法で呼び出します。
$ python -m sample_2.py
file: __main__
$ python
>>> import sample_2
file: sample_2
python -m sample_2
として実行した際はmainモジュールとしてみなされ、import
された際はmainモジュールにはなっていません。
その内容を main モジュールとして実行します。
これで、↑がその通りだったということがわかりました。
後半部分
このオプションが指定された場合、 sys.argv の最初の要素はモジュールファイルのフルパスになります (モジュールファイルを検索している間、最初の要素は "-m" に設定されます)。 -c オプションと同様に、カレントディレクトリが sys.path の先頭に追加されます。
sys.argv/sys.pathに追加されるパス
以下のようにモジュール(パスは/Users/ryku/Documents/python/research_py/lib/module.py
)を作成します。
import sys
print(f"__name__ is: {__name__}")
print(f"argv is: {sys.argv}")
print(f"path is: {sys.path}")
これを2種類の方法で実行してみましょう。実行するディレクトリは/Users/ryku/Documents/python/research_py
です。
$ python lib/module.py
__name__ is: __main__
argv is: ['lib/module.py']
path is: ['/Users/ryku/Documents/python/research_py/lib', '/Users/ryku/opt/anaconda3/lib/python38.zip', '/Users/ryku/opt/anaconda3/lib/python3.8', '/Users/ryku/opt/anaconda3/lib/python3.8/lib-dynload', '/Users/ryku/opt/anaconda3/lib/python3.8/site-packages', '/Users/ryku/opt/anaconda3/lib/python3.8/site-packages/aeosa']
$ python -m lib.module
__name__ is: __main__
argv is: ['/Users/ryku/Documents/python/research_py/lib/module.py']
path is: ['/Users/ryku/Documents/python/research_py', '/Users/ryku/opt/anaconda3/lib/python38.zip', '/Users/ryku/opt/anaconda3/lib/python3.8', '/Users/ryku/opt/anaconda3/lib/python3.8/lib-dynload', '/Users/ryku/opt/anaconda3/lib/python3.8/site-packages', '/Users/ryku/opt/anaconda3/lib/python3.8/site-packages/aeosa']
まずはargvを見てみましょう。sys.argv
は実行時に渡されたコマンドライン引数のリストを返します。
公式ドキュメントの記載通り、スクリプトとして実行した場合、実行ディレクトリからモジュールまでの相対パスになっているのに対して、モジュールで実行した場合は絶対パスになっています。
次にpathを見てみます。こちらも公式ドキュメント通り、スクリプトの実行ではモジュールが存在しているディレクトリへの絶対パスがsys.pathの先頭に追加されているのに対して、-mオプションで実行した場合はカレントディレクトリ(実行した場所)の絶対パスがsys.pathの先頭に追加されています。
長々と検証を述べてきましたが、やはり-mオプションの重要な点としては、スクリプトでの実行とで、このsys.path
とsys.argv
の先頭に追加されるパスが異なるという点になるかと思います。なので冒頭の説明通りですね。
結局何の利点があるのか?
なんかパスに追加されるものが違うのはわかったけど、結局何が嬉しいの?となると思います。
親ディレクトリのモジュールをインポートする必要のあるスクリプトの実行
パッと思いつくのはこれくらいですね..
自分の経験上では親ディレクトリのモジュールをインポートするスクリプトの実行時などにも-mオプション
を使用していました。
親ディレクトリのインポートはsys.path.append
してからimport package
する等の解決策もありますが、これはPEP8の規約に引っかかったりと色々面倒です。-m
オプションを使うならスクリプトには実行ディレクトリからのパスでインポートするモジュールを指定すれば良いのでよりシンプルかなと思ってます。
sys.path.append
の後にimport文を記述するとPEP8のModule level import not at top of file (E402)
という警告が出てきます。
付録:なんか__pycache__とかいうのができてるけど...
-m
オプションでpythonファイルを実行すると、__pychache__
という謎のファイルが生成されます。通常の実行では特に生成された記憶が無く、これはなんだろうと思っていました。
ChatGPTさんに聞いてみました。
--- Ryku(私) ---
pythonの__pycache__フォルダについて教えてください。
- __pycache__のメリット
- python -m sample.my_functionを実行した際に、__pycache__が生成されます。これは内部的にどう動いているのかまで具体的に説明
- 結び(何かPythonの学びになる一言を添えて)
という感じで解説をお願いします。
--- ChatGPT ---
理解しました。それでは、お願いされた内容に沿って説明いたします。
__pycache__とは、Pythonのバイトコードキャッシュ(事前にコンパイルされたPythonのソースコード)を格納するディレクトリです。このキャッシュは、再度実行する際にスクリプトを高速に実行するためのものです。
python -m sample.my_functionというコマンドを実行すると、「sample」というモジュールをインポートして「my_function」を実行するということになります。このとき、Pythonは「sample」モジュールに対する依存関係を調べ、それらを含むすべてのモジュールを探索します。このモジュールを探索するとき、「sample」モジュールのソースコードを再度コンパイルすることなく、__pycache__に保存されているバイトコードキャッシュを利用することができます。これにより、スクリプトの実行が高速になります。
結び:__pycache__はPythonのスクリプトの実行速度を向上させるためのもので、-mオプションを使って実行することでこのメリットを最大限に活用することができます。
すごい的確に説明してそうなのでこれで良いですね。(もう記事を書く必要はあるのか...)