[Pipenv] uninstallよりcleanの方が多分便利

最近，Pipenvは市民権を得てきていて，入門記事をよく見かけるようになっているような気がしています．
ただ，`pipenv uninstall`に比べて`pipenv clean`が紹介されていないことが多い気もしていて，「正直`pipenv clean`の方が便利じゃない？」と思っているので，その啓蒙です．

## 環境

多分クリティカルではないですが一応

- Python 3.6.8
- Pipenv 2018.11.26

## `pipenv uninstall`を常用することの問題点

パッケージの削除の仕方として，`pipenv uninstall [package(s)]`が入門記事でよく紹介されていますが，このコマンドは依存関係を見ずに削除します．そのため，このコマンドを常用するのには少し問題があると思っています．
といってもわかりづらいと思うので，具体例を2つほど挙げてみます．

### 具体例1

1つ目は，`pipenv uninstall`しても，そのパッケージが依存しているライブラリは（ローカルでは）削除されないということです．
例えば以下のように，`pipenv install`して，`pipenv uninstall`しても，scikit-learnの依存パッケージである，joblibやscipyは削除されずに残ってしまいます．

```sh
$ pipenv install scikit-learn
$ pipenv uninstall scikit-learn
$ pipenv graph
joblib==0.13.2
scipy==1.3.1
  - numpy [required: >=1.13.3, installed: 1.17.2]
```

とはいえ，`Pipfile.lock`は以下のように，ちゃんと綺麗になっているので，チーム開発をしていて他者に影響を与えることはありません．
自分は変に潔癖なのでこの辺り大変気になるのですが，気にならない人は気にならないと思います．

```Pipfile.lock
{
    "_meta": {
        "hash": {
            "sha256": "415dfdcb118dd9bdfef17671cb7dcd78dbd69b6ae7d4f39e8b44e71d60ca72e7"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.6"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {},
    "develop": {}
}
```

### 具体例2

こちらの方が重要だと思いますが，他パッケージがそれに依存していても消せてしまう点です．
例えば，以下のような感じです．

```sh
$ pipenv install scikit-learn numpy
$ pipenv uninstall numpy
$ pipenv run python3 -c 'import sklearn'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/yuma/.local/share/virtualenvs/pipenv-mDs4mp0Y/lib/python3.6/site-packages/sklearn/__init__.py", line 76, in <module>
    from .base import clone
  File "/home/yuma/.local/share/virtualenvs/pipenv-mDs4mp0Y/lib/python3.6/site-packages/sklearn/base.py", line 13, in <module>
    import numpy as np
ModuleNotFoundError: No module named 'numpy'
```

わざとらしく感じるかもしれませんが，同様なことをpytorchとnumpyで一回やらかしました．
流れとしては，

1. 初めはnumpyを直接使う気でいたので，Pipfileに書いた．
2. やっぱ使わないわ，（Pipfileから）消すか．`pipenv uninstall numpy`
3. uninstallされたので，当然`import torch`でエラー

まあこちらについても，`Pipfile.lock`上はちゃんとnumpyも生きているので，自分が多少時間を取られるだけですが．

## `pipenv clean`とは

`pipenv clean -h`すると，

> Uninstalls all packages not specified in Pipfile.lock.

と表示される通り，Pipfile.lockに書かれていないパッケージを全て削除するコマンドです．Pipfile.lockが古いと，再ロックするようなので，`pipenv clean`する前にPipfileを編集しておくことで，パッケージの削除が（多分安全に）行えます．

## `pipenv clean`を使うと

先に挙げた具体例で代わりに`pipenv clean`を用いた場合を示します．

### 具体例1

以下のコマンド既に打った状態を想定します．

```sh
$ pipenv install scikit-learn
```

やることは，Pipfileのscikit-learnの行を削除して，`pipenv clean`するだけです．

```diff
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
- scikit-learn = "*"

[requires]
python_version = "3.6"
```

するとちゃんと，依存パッケージも全て削除されます．

```sh
$ pipenv clean
Pipfile.lock (25c533) out of date, updating to (ca72e7)…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (ca72e7)!
Uninstalling scipy…
Uninstalling scikit-learn…
Uninstalling numpy…
Uninstalling joblib…
$ pipenv graph
# なにも出ない
```

### 具体例2

同様に以下のコマンド既に打った状態を想定します．

```sh
$ pipenv install scikit-learn numpy
```

Pipfileのnumpyの行だけを削除して，`pipenv clean`してみます．

```diff
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
scikit-learn = "*"
- numpy = "*"

[requires]
python_version = "3.6"
```

するとこちらでは，ちゃんと依存パッケージを見ているので，numpyは削除されずに残っていることがわかります．

```sh
$ pipenv clean                                                                                                                                 3079ms
Pipfile.lock (3b103b) out of date, updating to (25c533)…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
✔ Success! 
Updated Pipfile.lock (25c533)!
$ pipenv run python3 -c 'import sklearn; print(sklearn.__version__)'
0.21.3
```

## まとめ

- `pipenv clean`の`pipenv uninstall`に対する優位性を示した．
- Pipenv詳しくないので，`pipenv clean`にも罠があるようなら教えていただければ幸いです．