Poetryでsys.path.appendを回避する
まとめ
pyproject.toml
の[tool.poetry]
のpackages
に以下のようにモジュールを追加しましょう。
packages = [
{ include = "hoge" },
{ include = "fuga" },
]
背景から詳しく
Pythonのモジュールシステムは他の言語と結構な違いがあり、どのファイルからどのモジュールをimportできるのかで頭を悩ませることが多々あります。
例えば、次のようなディレクトリ階層を持つプロジェクトについて考えてみましょう。
.
├── pyproject.toml
├── hoge
│ └── a.py
├── fuga
└── b.py
hoge/a.py
とfuga/b.py
はそれぞれ以下のようになっています。
print(__file__)
import hoge.a
# 何かを実行
ここで、プロジェクトルートにいる状況で、python fuga/b.py
を実行したいとします。
fuga/b.py
はhoge/a.py
をimportしていますが、通常、このままではimportに失敗します。
できればプロジェクトルートからのパスでimportできると複雑さを軽減できるのですが、そのためにはプロジェクトルートをsys.path
に追加してあげる必要があり、結果として以下のような美しくないプログラムが出来上がりがちです。
import sys
sys.path.append(".")
import hoge.a
# 何かを実行
この問題の原因はsys.path
にカレントディレクトリ(プロジェクトルート)が追加されておらず、hoge
モジュールが見えないことが原因です。
この問題の解決法として、環境変数のPYTHONPATH
を使うことができます。
PYTHONPATH
はPythonがsys.path
に追加すべきパスのリストで(参考)、これを使うことで明示的なsys.path.append
を実行しなくてもsys.path
にカレントディレクトリを追加することができます。
PYTHONPATH
を用いた場合のfuga/b.py
の実行方法は以下の通りです。
# `import sys` などは不要
import hoge.a
# 何かを実行
PYTHONPATH=.:$PYTHONPATH python fuga/b.py
これで煩雑なsys.path.append
を抑制することができましたが、今度はPythonコマンド実行のたびに環境変数を指定する必要が出てきてしまいました。
これでは面倒臭さが軽減されていません。
そこで、sys.path
にうまく追加するのではなく、別のツールを使って解決できないかを考えてみます。
そのような場合に使えるのが、Pythonのパッケージマネージャ 兼 依存関係管理用のツールであるPoetryです。
Poetryを用いることで、依存関係の管理や仮想環境の準備、パッケージのビルドなど様々な処理を簡単に実行できます。
今回はPoetryの設定ファイルを書くことで、これらのパッケージのimportをできるようにしてみます。
Poetryの設定にはpyproject.toml
の[tool.poetry]
を使いますが、その中のpackages
というセクションが今回の場合に使えます。
このセクションはプロジェクトをPythonパッケージとして公開する際に使えるものですが、開発環境においてどのパッケージを仮想環境に入れるか、という用途でも使うことができます。
具体的には、今回の場合以下のように設定を行います。
[tool.poetry]
name = "..."
version = "x.y.z"
packages = [
{ include = "hoge" },
{ include = "fuga" },
]
この設定を記述した後、poetry install
を実行します。
これにより、プロジェクトルートディレクトリがsys.path
に自動的に含まれるようになり、結果として先ほどの例を問題なく実行できるようになります。
Poetryを用いてPythonを実行するには、以下のようにすればよいです。
import hoge.a
# 何かを実行
poetry run python fuga/b.py
すると、実行結果は以下のようになります。
/path/to/hoge/a.py
fuga/b.py
無事、importが成功し、実行できていることがわかりました。
考察
さて、ここまでPoetryのpackages
セクションを用いてこのような動作を成功させましたが、実はちょっと不思議な挙動をしています。
時間がある方は、以下のようにpackages
に片方だけを記述し、.venv
を削除したのちにpoetry install
をして同様の実験をしてみてください。
[tool.poetry]
name = "..."
version = "x.y.z"
packages = [
{ include = "fuga" },
]
おそらく、poerty run python fuga/b.py
は成功すると思います。
なぜ、hoge
が追加されていない(何にかはよくわからない)のにもかかわらず、実行に成功したのでしょうか。
これは、packages
に記載したinclude
の親ディレクトリがsys.path
に追加されるためです。
packages
にはinclude
のほかにfrom
という設定を記述することができ、実際にはfrom
のディレクトリがsys.path
に追加されていそうです(ちゃんと裏をとっていないので、訂正があればお教えいただけると助かります)。
したがって、このように片方だけをpackages
に記載したとしても、プロジェクトルートがsys.path
に追加されるので、問題なくhoge
を見つけることができる、というわけです。
なので、例えばプロジェクトルートにsrc
があるような一般的なPythonプロジェクトの構成であれば以下のように1行追加するだけでよい可能性が高いです。
[tool.poetry]
name = "..."
version = "x.y.z"
packages = [
{ include = "src" },
]
これらの設定をどのように記述するべきかはプロジェクトによって変化しそうなので、もし「このケースではこうする必要があった」という話があれば、他の方のためにもぜひこの記事のコメント欄に書いていただけると幸いです。
この記事は以上になります。
ここまで読んでいただきありがとうございました。
みなさんもよきPython生活を。
Discussion