8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

pythonの-mオプションの動作を調べる

Last updated at Posted at 2023-02-04

-mオプションでの動作

通常のスクリプトでの実行との主な違いとしては、以下のようにsys.argvsys.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ファイルを作成します。

sample_2.py
print(f"file: {__name__}")

__name__という変数を使用して、そのファイルのモジュール名を取得できます。

このモジュールを2種類の方法で呼び出します。

-mオプションで実行する
$ python -m sample_2.py 
file: __main__
モジュールをimportする
$ 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)を作成します。

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.pathsys.argvの先頭に追加されるパスが異なるという点になるかと思います。なので冒頭の説明通りですね。

結局何の利点があるのか?

なんかパスに追加されるものが違うのはわかったけど、結局何が嬉しいの?となると思います。

question_head_boy.png

親ディレクトリのモジュールをインポートする必要のあるスクリプトの実行

パッと思いつくのはこれくらいですね..
自分の経験上では親ディレクトリのモジュールをインポートするスクリプトの実行時などにも-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オプションを使って実行することでこのメリットを最大限に活用することができます。

すごい的確に説明してそうなのでこれで良いですね。(もう記事を書く必要はあるのか...)

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?