nikkie-ftnextの日記

イベントレポートや読書メモを発信

Pythonライブラリをpipで管理するとき、uninstallが私にはツラい

はじめに(なにか始まったぞ...)

(この物語は、Python 3.11.8で作った仮想環境でお届けします)

あるところにtransformersをインストールして開発を進めるPythonプロジェクトがありました。

% pip install transformers

そのプロジェクトではrouge-scoreも追加でインストールしました1

% pip install rouge-score

プロジェクトが進む中でrouge-scoreだけをアンインストールすることになりました。
さあどうしますか?

目次

単にpip uninstallしては?

Pythonチュートリアルではpip uninstall rouge-scoreが案内されます。
https://docs.python.org/ja/3/tutorial/venv.html#managing-packages-with-pip

pip installの逆操作ですね。
しかしこれだと不都合があります。

% pip uninstall rouge-score
Found existing installation: rouge_score 0.1.2
Uninstalling rouge_score-0.1.2:
  Would remove:
    /.../.venv/lib/python3.11/site-packages/rouge_score-0.1.2.dist-info/*
    /.../.venv/lib/python3.11/site-packages/rouge_score/*
Proceed (Y/n)? Y
  Successfully uninstalled rouge_score-0.1.2

pip uninstall rouge-scoreではrouge-scoreしかアンインストールされません
rouge-scoreが依存するライブラリも不要になっていますが、残っているのです。

transitiveな依存

以下の記事の中でtransitiveという語を使いました。

ルーツはこちらです。

今回のPythonプロジェクトの場合、直接(direct)の依存がtransformersとrouge-scoreです。
transformersとrouge-scoreが依存する数々のライブラリが(プロジェクトから見て)transitiveな依存となります。

rouge-scoreをアンインストールする場合、rouge-scoreが依存するライブラリ(プロジェクトから見てtransitiveな依存)もアンインストールしたいです。
しかしながら、rouge-scoreとtransformersとで共通の依存ライブラリは(transformersで使うので)アンインストールしたくはありません

箇条書きでまとめてみます

  • プロジェクトの直接の依存
    • transformers
    • rouge-score
  • rouge-scoreをアンインストールする
  • rouge-scoreの依存(プロジェクトから見てtransitiveな依存)のうち
    • rouge-scoreのみが依存するライブラリはアンインストール
    • transformersも依存ライブラリは残す(アンインストールしたら環境が壊れてしまうため)

transitiveな依存の一部を残すのがツラい

私の知っている方法を記録のために書きます(アップデートしたい!)

transitiveな依存を把握するためにツールを使います。

CLIから使うPythonライブラリなのでpipx2で入れました(pipx install)。

依存を木構造で書き出します。

% pipdeptree --python .venv/bin/python
pip==24.0
rouge_score==0.1.2
├── absl-py [required: Any, installed: 2.1.0]
├── nltk [required: Any, installed: 3.8.1]
│   ├── click [required: Any, installed: 8.1.7]
│   ├── joblib [required: Any, installed: 1.4.0]
│   ├── regex [required: >=2021.8.3, installed: 2024.4.16]
│   └── tqdm [required: Any, installed: 4.66.2]
├── numpy [required: Any, installed: 1.26.4]
└── six [required: >=1.14.0, installed: 1.16.0]
setuptools==69.5.1
transformers==4.40.1
├── filelock [required: Any, installed: 3.13.4]
├── huggingface-hub [required: >=0.19.3,<1.0, installed: 0.22.2]
│   ├── filelock [required: Any, installed: 3.13.4]
│   ├── fsspec [required: >=2023.5.0, installed: 2024.3.1]
│   ├── packaging [required: >=20.9, installed: 24.0]
│   ├── PyYAML [required: >=5.1, installed: 6.0.1]
│   ├── requests [required: Any, installed: 2.31.0]
│   │   ├── certifi [required: >=2017.4.17, installed: 2024.2.2]
│   │   ├── charset-normalizer [required: >=2,<4, installed: 3.3.2]
│   │   ├── idna [required: >=2.5,<4, installed: 3.7]
│   │   └── urllib3 [required: >=1.21.1,<3, installed: 2.2.1]
│   ├── tqdm [required: >=4.42.1, installed: 4.66.2]
│   └── typing_extensions [required: >=3.7.4.3, installed: 4.11.0]
├── numpy [required: >=1.17, installed: 1.26.4]
├── packaging [required: >=20.0, installed: 24.0]
├── PyYAML [required: >=5.1, installed: 6.0.1]
├── regex [required: !=2019.12.17, installed: 2024.4.16]
├── requests [required: Any, installed: 2.31.0]
│   ├── certifi [required: >=2017.4.17, installed: 2024.2.2]
│   ├── charset-normalizer [required: >=2,<4, installed: 3.3.2]
│   ├── idna [required: >=2.5,<4, installed: 3.7]
│   └── urllib3 [required: >=1.21.1,<3, installed: 2.2.1]
├── safetensors [required: >=0.4.1, installed: 0.4.3]
├── tokenizers [required: >=0.19,<0.20, installed: 0.19.1]
│   └── huggingface-hub [required: >=0.16.4,<1.0, installed: 0.22.2]
│       ├── filelock [required: Any, installed: 3.13.4]
│       ├── fsspec [required: >=2023.5.0, installed: 2024.3.1]
│       ├── packaging [required: >=20.9, installed: 24.0]
│       ├── PyYAML [required: >=5.1, installed: 6.0.1]
│       ├── requests [required: Any, installed: 2.31.0]
│       │   ├── certifi [required: >=2017.4.17, installed: 2024.2.2]
│       │   ├── charset-normalizer [required: >=2,<4, installed: 3.3.2]
│       │   ├── idna [required: >=2.5,<4, installed: 3.7]
│       │   └── urllib3 [required: >=1.21.1,<3, installed: 2.2.1]
│       ├── tqdm [required: >=4.42.1, installed: 4.66.2]
│       └── typing_extensions [required: >=3.7.4.3, installed: 4.11.0]
└── tqdm [required: >=4.27, installed: 4.66.2]

出力はファイルにリダイレクトして参照しました。
アンインストールしたいrouge-scoreについて、木構造にあたるライブラリを確認します。

  • rouge-scoreのみに依存するtransitiveな依存(=一緒にアンインストールできる)
    • absl-py
    • click
    • joblib
    • (葉ではないが nltk)
    • six
  • transformersにも依存するtransitiveな依存(=アンインストールしてはならない)

というわけで、rouge-scoreと一緒にアンインストールできるライブラリも消すコマンドは

% pip uninstall -y rouge-score absl-py click joblib nltk six

となります

終わりに

依存ライブラリをpipで管理すると、直接の依存をアンインストールするときに、一緒にアンインストールできるtransitiveな依存を把握するのが大変なことを書きました。

思うに、pip以外のツールが必要なタイミングなのだと思います。
Poetry、Pipenvなどなど色々なツールがありますよね。
直接の依存をaddしたらremoveはツールに任せられる、という理解です。

単一のスクリプトを開発する場合なら直接の依存の増減はあまりないのでpipで事足りてきたのではないかと思うのですが、より大きなアプリケーションの開発では直接の依存を変更しやすくするためにツールの使用を検討したいですね