はじめに
盂蘭盆会👋 nikkieです。
Pythonの環境、特に依存ライブラリを再現する経験を最近しました。
目次
- はじめに
- 目次
- Pythonの伸びしろ:requirements.txt
- 2種類の依存:direct と transitive
- pipdeptree でdirectな依存を取り出す
- 終わりに
- 関連記事
Pythonの伸びしろ:requirements.txt
Pythonはとても私の手に馴染む言語で今後も使い続けると思いますが、明確に伸びしろと感じている点があります。
それが環境の再現。
ここ最近は仮想環境について話すことが多いですが、チュートリアルで案内される方法は「パッケージマネージャ、俺」です(つまりは人力ということ😫)
環境の再現も私たち(=人力)頼みで案内されます1。
https://docs.python.org/ja/3/tutorial/venv.html#managing-packages-with-pip
$ python -m pip freeze > requirements.txt $ python -m pip install -r requirements.txt
requirements.txt をバージョン管理システムにコミットして、アプリケーションの一部として配布することができます。
https://ftnext.github.io/2024-slides/pyconkyushu/virtual-environment-with-pip-tools.html#/8/2
このrequirements.txtなのですが、人間が指定するので表記揺れがありえます2。
またpip freeze
せずにも作れるので、全ての依存を含まないrequirements.txtが生まれてしまうこともあります。
https://ftnext.github.io/2024-slides/techramen/python-package-management-tools.html#/5/7
いま手元には、全ての依存を含んでいると思われるrequirements.txtがあるとします(先人に誠に感謝)。
このrequirements.txtで環境を再現したうえで、ライブラリを切り替えやすい状況にしていきます。
2種類の依存:direct と transitive
さて、ここで「依存」について少し掘り下げます。
依存には、実は2種類あります
- directな依存
- 私たちが
pip install
で指定するパッケージ
- 私たちが
- transitiveな依存
- directな依存が依存するパッケージ
used with a direct object (Oxford辞書より)
https://ftnext.github.io/2024-slides/pyconkyushu/virtual-environment-with-pip-tools.html#/9
pip freeze
して作られたrequirements.txtには、directな依存もtransitiveな依存も含まれます。
ここからdirectな依存(と関連するtransitiveな依存)をアンインストールするのが難しいと感じます
directな依存とtransitiveな依存を区別すれば、新たなdirectな依存を追加したり、使わなくなったdirectな依存を削除したりといったことはだいぶやりやすくなります。
そこで、requirements.txtからdirectな依存の仕分けを実施します。
pipdeptree でdirectな依存を取り出す
今回のユースケースはREADMEに取り上げられていました。
https://github.com/tox-dev/pipdeptree/tree/2.23.1?tab=readme-ov-file#using-pipdeptree-to-write-requirementstxt-file
If you wish to track only top level packages in your requirements.txt file, it's possible by grep-ing
空の仮想環境にrequirements.txtをインストールし、pipdeptree
を実行します。
pipdeptree
はpipx
でインストールしています。
% python -m venv .venv --upgrade-deps % source .venv/bin/activate (.venv) % python -m pip install -r requirements.txt (.venv) % pipdeptree --python .venv/bin/python
これで依存ツリーが出力されます。
ここからgrep
を使ってdirectな依存(=上記の「top level packages」)を取り出します。
(.venv) % pipdeptree --python .venv/bin/python | grep -E '^\w+' | ggrep -o -P '.*(?===)'
ggrep
の部分は、バージョンを削って出力するための肯定先読みです(awesomelib==0.1.0
のawesomelib
だけ取り出す)
directな依存を取り出した後は吟味しました。
作業を終えて気付いたのは、pip freeze
したrequirements.txtにはextra (=optional dependencies) が記録されていないということです。
そのためdirectな依存っぽくないパッケージを除く必要がありました。
しかしここはPython開発経験が必要で、誰でもできるかは分からない手順になっちゃってますね。
例えば手元で見つけたのは、cryptography。
これはextraの指定で入ってきていたことが分かったのですが、pip freeze
したrequirements.txtではその情報は消えていました。
手元に残ったdirectな依存を元に、パッケージ管理ツールを導入します。
今回のシーンでは、pip-toolsを選択しました。
pip-toolsはrequirements.inやpyproject.tomlを元にrequirements.txtを生成(pip-compile
)してから、依存をインストールする(pip-sync
)点が、開発をとてもやりやすくしてくれると感じます(pip freeze
とは真逆!)
終わりに
全ての依存が出力された(pip freeze
製の)requirements.txtから、仮想環境を再現し、directな依存をpipdeptreeで仕分けました。
directな依存を特定した後は、(本文ではpip-toolsでしたが)お好みのパッケージ管理ツールを導入できると思います。
pip freeze
の出力のrequirements.txtがあること自体はありがたいのですが、pip freeze
の時点でdirectな依存のextraの指定情報は消えてしまっているので、やはり言語として伸びしろがあると考えます(アイデアはいまはないけど、プルリクチャンス!)
pip freeze
の出力のrequirements.txtはあくまで一時的な組合せにすぎず、依存ライブラリのバージョンアップに追従していきたいと考えています3。
更新していく必要があるという点で、directな依存は、パッケージにおける依存(pyproject.tomlのdependencies)に近いように思われました
関連記事
スイッチサイエンスさんのテックブログより
importlib.metadataを使った例です
(pipdeptreeも中ではimportlib.metadataを使っているのかな?)