はじめに
Act-4、最高だった...😭 余韻の真っ只中のnikkieです。
表題の件について、違いを知ったので記します。
鍵を握るのはsys.pathです
目次
- はじめに
- 目次
- pytestのドキュメントより
- sys.pathのドキュメント
- 結論:pytestとpython -m pytestはここが違う!
- sys.pathが変わることを動作確認
- 思い出した過去記事
- 終わりに
pytestのドキュメントより
ドキュメント「How to invoke pytest」に「Calling pytest through python -m pytest
」があります。
https://docs.pytest.org/en/8.0.x/how-to/usage.html#calling-pytest-through-python-m-pytest
python -m pytest [...]
This is almost equivalent to invoking the command line script
pytest [...]
directly, except that calling via python will also add the current directory to sys.path.
pytest [...]
との違いは、python -m pytest [...]
が現在のディレクトリをsys.pathに追加する点です。
この点以外は同じと理解しました。
sys.pathのドキュメント
https://docs.python.org/ja/3/library/sys.html#sys.path
モジュールを検索するパスを示す文字列のリスト。
ここに記載があります。
python -m module
command line: prepend the current working directory.
現在の作業ディレクトリをリストの先頭に追加する
PyCon APACのトークでも聞いていました。
sys.pathの先頭には
カレントディレクトリがセットされる
結論:pytestとpython -m pytestはここが違う!
- モジュールが提供するコマンド(例:
pytest
)は、sys.pathを変更しない python -m module
(例:python -m pytest
)は、sys.pathの先頭に現在のディレクトリを追加するという変更をする
sys.pathの変更以外は同じです
sys.pathが変わることを動作確認
簡単なライブラリを作って確認します。
こういうとき、(ライブラリの雛形があるという点で)unko1は役に立ちますね。
- Python 3.12.0
sys.pathを確認する実装
unko/__main__.py
を実装しました
import sys def main(): for path in sys.path: print(path) if __name__ == "__main__": main()
pyproject.tomlでunko
コマンドを定義します
[project.scripts] unko = "unko.__main__:main"
全容はこちらからどうぞ:
https://github.com/ftnext/unko/compare/main...example/python-m
動作確認
違いが分かりやすいようにunkoのルートではなく、tmpディレクトリを作ってそこで実行しました。
% unko /.../unko/.venv/bin /.../.pyenv/versions/3.12.0/lib/python312.zip /.../.pyenv/versions/3.12.0/lib/python3.12 /.../.pyenv/versions/3.12.0/lib/python3.12/lib-dynload /.../unko/.venv/lib/python3.12/site-packages
(仮想環境.venvのbinがsys.pathに入っているのは、また別の理由がありそうですね)
% python -m unko /.../unko/tmp /.../.pyenv/versions/3.12.0/lib/python312.zip /.../.pyenv/versions/3.12.0/lib/python3.12 /.../.pyenv/versions/3.12.0/lib/python3.12/lib-dynload /.../unko/.venv/lib/python3.12/site-packages
単にunko
コマンドとの違いは、カレントディレクトリ(/.../unko/tmp
)がsys.pathの先頭にあるかいなかですね。
思い出した過去記事
(1)コマンドラインで実行だけするPythonアプリケーションについては、pipxというツールで管理する方法があります。
原理的にはpython -m
で実行もできるわけですが、我が身を振り返るとコマンドで実行することが多いですね。
コマンド自体がきれいに作られているから、でしょうか。
(2)sys.pathは環境変数PYTHONPATHでも設定できる
% PYTHONPATH=/spam/ham/egg unko /.../unko/.venv/bin /spam/ham/egg /.../.pyenv/versions/3.12.0/lib/python312.zip /.../.pyenv/versions/3.12.0/lib/python3.12 /.../.pyenv/versions/3.12.0/lib/python3.12/lib-dynload /.../unko/.venv/lib/python3.12/site-packages
% PYTHONPATH=/spam/ham/egg python -m unko /.../unko/tmp /spam/ham/egg /.../.pyenv/versions/3.12.0/lib/python312.zip /.../.pyenv/versions/3.12.0/lib/python3.12 /.../.pyenv/versions/3.12.0/lib/python3.12/lib-dynload /.../unko/.venv/lib/python3.12/site-packages
python -m
を実行したカレントディレクトリは、PYTHONPATHで指定したパスより前に入るんですね。
終わりに
pytestとpython -m pytestや、pip installとpython -m pip installの違いを知りました。
ぼんやりと「使い手の好みくらいしか差はないんじゃないか」と考えていましたが、sys.pathにカレントディレクトリを追加するかしないかという振る舞いの違いがありました。
pipxに心惹かれているのもあって私自身はpython -m pytest
のようなコマンドはあまり利用しないんじゃないかと思いますが、裏でsys.pathを変更しているという点は心に留めておきたいと思っています
- 出自 ↩