pipでの依存性管理を少しだけラクにしてみる

# はじめに

PythonBytes ポッドキャストの[最新エピソード（第208回）](https://pythonbytes.fm/episodes/show/208)を聴いていて面白そうなツールの紹介をしていたので試してみました。pipでの依存性管理が少しだけラクになると思います。

# pipでの依存性管理とその課題

まずpipでの依存性管理について基本的なおさらいを。必要なパッケージをインストールするには `pip install`で行います。例えば、`requests`と`pandas`という2つのパッケージをインストールすると以下のようになります。

```shell
$ pip install requests
Collecting requests
  Downloading requests-2.25.0-py2.py3-none-any.whl (61 kB)
     |████████████████████████████████| 61 kB 7.2 MB/s
Collecting urllib3<1.27,>=1.21.1
  Downloading urllib3-1.26.2-py2.py3-none-any.whl (136 kB)
     |████████████████████████████████| 136 kB 10.5 MB/s
Collecting certifi>=2017.4.17
  Downloading certifi-2020.11.8-py2.py3-none-any.whl (155 kB)
     |████████████████████████████████| 155 kB 12.9 MB/s
Collecting chardet<4,>=3.0.2
  Using cached chardet-3.0.4-py2.py3-none-any.whl (133 kB)
Collecting idna<3,>=2.5
  Using cached idna-2.10-py2.py3-none-any.whl (58 kB)
Installing collected packages: urllib3, certifi, chardet, idna, requests
Successfully installed certifi-2020.11.8 chardet-3.0.4 idna-2.10 requests-2.25.0 urllib3-1.26.2
```

```shell
$ pip install pandas
Collecting pandas
  Downloading pandas-1.1.4-cp39-cp39-macosx_10_9_x86_64.whl (10.3 MB)
     |████████████████████████████████| 10.3 MB 5.9 MB/s
Collecting numpy>=1.15.4
  Using cached numpy-1.19.4-cp39-cp39-macosx_10_9_x86_64.whl (15.4 MB)
Collecting pytz>=2017.2
  Downloading pytz-2020.4-py2.py3-none-any.whl (509 kB)
     |████████████████████████████████| 509 kB 31.7 MB/s
Collecting python-dateutil>=2.7.3
  Downloading python_dateutil-2.8.1-py2.py3-none-any.whl (227 kB)
     |████████████████████████████████| 227 kB 33.8 MB/s
Collecting six>=1.5
  Using cached six-1.15.0-py2.py3-none-any.whl (10 kB)
Installing collected packages: numpy, pytz, six, python-dateutil, pandas
Successfully installed numpy-1.19.4 pandas-1.1.4 python-dateutil-2.8.1 pytz-2020.4 six-1.15.0
```

これを見ると指定したパッケージだけでなくそれが依存しているパッケージも自動的に引っ張ってきてくれます。インストールされているパッケージのリストが見たければ `pip freeze`を使います。

```shell
$ pip freeze
certifi==2020.11.8
chardet==3.0.4
idna==2.10
numpy==1.19.4
pandas==1.1.4
python-dateutil==2.8.1
pytz==2020.4
requests==2.25.0
six==1.15.0
urllib3==1.26.2
```

これは別にPythonで決められていることではないですが、多くの人がこの結果を `requirements.txt`というファイルに書き出しておいて、あとから再度インストールする時や他の人が使うときに一括でインストール出来るようにしています。

```shell
$ pip freeze > requirements.txt
$ pip install -r requirements.txt
```

`pip freeze`の結果は`pip install`したパッケージだけでなく、依存したパッケージも同じようにリストされています。さらに各パッケージのバージョンも入っていて、`pip install -r `するとインストールした時と同じ全く同じ環境を作り出すことができます。他のツールだとlockファイルと称するものも多いと思いますが、pipの場合は「freeze (凍らせる)」という言い方をしているわけですね。

同じ環境を再現するという意味で全てのパッケージが羅列されているのは良いのですが、これだけ見るとどれが自分がインストールしたもので、どれが依存関係で引っ張られてきたのかがわかりません。パッケージをアンインストールする際に自分が`package install`したものは消せてもそれが引っ張ってきたパッケージがどれだったかがわからないと消すのが難しい。また、依存するパッケージにバグ修正があったとしても古いバージョンを使い続けてしまうという問題もあります。そこで、次の章でご紹介するツールです。

# pip-chill 

`pip freeze`の不便さを補うツールとして pip-chillというツールがあります。これは`pip freeze`とは違ってpipには組み込まれていないので単独でインストールする必要があります。

```shell
$ pip install pip-chill
```

これで pip-chillが使えるようになります。引数無しで実行するとこうなります。

```shell
$ pip-chill
pandas==1.1.4
pip-chill==1.0.0
requests==2.25.0
```

`pip freeze`と似ていますが、自分でインストールしたパッケージのみが表示されています。「何をインストールしていたっけ？」というときにひと目で分かるので便利ですね。

`pip-chill`がこのリストに入ってしまっているのが玉に瑕で、ポッドキャストでもブライアンさんが「リストからpip-chill自身を隠すオプションあったら良かったのに」って言っていましたが、`pip-chill | grep -v pip-chill`すればとりあえず回避できます。

pip-chillにはオプションがいくつかあり、例えば `--no-version`を使うとパッケージ名だけを表示してくれます。

```shell
$ pip-chill --no-version
pandas
pip-chill
requests
```

例えば開発中の段階ではあまり細かくバージョン指定とかする必要ないと思うのでこれを `requirements.txt`に入れておくというのはあると思うんですよね。途中で不要になって抜いたり、同じような機能のパッケージを複数試してみたりの時に簡単にできるように。それで、テストも終えてコレでよし！となったら `pip freeze > requirements.txt`して依存パッケージも含めて固定すると。

もう一つ有用なオプションがあって、それを使うとこんな出力になります。

```shell
$ pip-chill -v
pandas==1.1.4
pip-chill==1.0.0
requests==2.25.0
# certifi==2020.11.8 # Installed as dependency for requests
# chardet==3.0.4 # Installed as dependency for requests
# idna==2.10 # Installed as dependency for requests
# numpy==1.19.4 # Installed as dependency for pandas
# python-dateutil==2.8.1 # Installed as dependency for pandas
# pytz==2020.4 # Installed as dependency for pandas
# six==1.15.0 # Installed as dependency for python-dateutil
# urllib3==1.26.2 # Installed as dependency for requests
```

通常の出力の後に依存関係でインストールされたパッケージのリストがコメントアウトの形で並んでいて、そこに「どのパッケージから依存されているか」という情報を出してくれています。いや、こういうのが欲しかったです。

さらに `-a`すると全てのパッケージを表示してくれます（つまり`pip freeze`と同じ）。

なお、pip-chillは `pip install`した履歴を覚えていて処理しているわけではなく「誰からも依存関係が張られていないもの（つまりは手動でインストールしたであろうもの）」を表示しているだけです。単純な仕組みでやりたいことを実現していて良いですね。ただそんな仕組みなので、例えば上の状態からpandasをアンインストールするとこうなります。

```shell
$ pip uninstall pandas
$ pip-chill
numpy==1.19.4
pip-chill==1.0.0
python-dateutil==2.8.1
pytz==2020.4
requests==2.25.0
```

当たり前ですが、pandasがいなくなったのでそこから依存関係を張られていたnumpy, python-dateutil, pytz が上に出てきちゃってます。pandasを消した時にこれらも一緒に消せるとさらに便利になるんですけどね。

なお、"Chill" というのは「冷たくする」という意味があります。日本語だと冷蔵庫に「チルド」という機能がありますが、それの語源（？）ですね。Freeze（凍らせる）までは行かないけど、その手前まで冷やすという感じで、上手いネーミングかと思います。

# まとめ

pip-chillというツールを使ってみました。`pip freeze`にこんな機能ないのかなと思っていたことをやってくれます。いずれpipに取り込まれないかな…。