【図で理解】PipenvによるPython仮想環境の管理

Pipenvの使い方がいまいちちゃんとわかっていなかったので、公式ドキュメント中心に読みながら色々動かしてみた時のメモです。

各コマンドの関連を図示したのでその流れで書いていきます。図中の番号とこの記事の段落の番号が対応しています。

<img width="1365" alt="スクリーンショット 2020-01-01 20.10.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113133/ec6e9d79-229d-da8c-f866-dcf195f160a8.png">


# Pipenv

Pythonの仮想環境は従来pipとvertualenvによって構築することが多かったですが、Pipenvはそれらを簡単にまとめて管理できるようにしたツールです。

- 内部的にはpipとvertualenvを使っている
- パッケージの依存関係まで管理し、ビルドが常に同じ結果をもたらすようになっている
- requirements.txtではなく、PipfileとPipfile.lockというファイルを活用する
- bundler、composer、npm、cargo、yarn等に近い

# PipfileとPipfile.lock

Pipenvは2つのファイルを生成します。これらを直接編集することは基本的に無いですが、仮想環境がどのような根拠でどのような状態になっているかを把握するためにそれぞれの役割を抑えておきます。それぞれのファイルには下記のような役割や性質があります。

### Pipfile
- 各種パッケージとそのバージョンを管理
- 開発用パッケージを分けて管理
- 自身で定義したスクリプトを管理

### Pipfile.lock
- インストールしたパッケージが依存しているパッケージとそのバージョンを管理
- リモートでのパッケージの改ざんから守るためのハッシュの管理（pipのバージョン8.0から導入された機構）

それでは、各コマンドとその使い方を見ていきます。

# 0. 準備

Pipenvをインストールします。

```shell
$ brew install pipenv
```

pipでインストールすることも可能です。

```shell
$ pip install pipenv
```

Pythonのバージョンを指定して仮想環境の初期化を行います。もし指定したバージョンが無い場合、pyenv経由でそのバージョンをインストールしてくれます。

```shell
$ pipenv --python 3.6
```

仮想環境が初期化されると、Pipfileというファイルが生成されます。

# 1. install

installコマンドでパッケージのインストールが可能です。

## 既存のPipfileやPipfile.lockを利用する

コマンドを実行する同階層にPipfileやPipfile.lockファイルがある場合、それらのファイルを参照して環境が構築されます。

```shell
$ pipenv install
```

## 既存のrequirements.txtを利用する

元々Pipenvで管理していなくても、requirements.txtからPiprnvで管理された環境を構築できます。

```shell
$ pipenv install -r ./requirements.txt
```

## 新しく環境を構築する

参照元となるファイルがない場合、先程の `pipenv --python <version>` 同様に環境が構築されます。

```shell
$ pipenv install --python 3.6
```

installコマンドの場合、Pipfileに加えPipfile.lockファイルも生成されています。中身を見てみましょう。


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

[dev-packages]

[packages]             // まだパッケージをインストールしていないので空

[requires]
python_version = "3.6" // バージョン3.6が指定されている
```

```json: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": {} 
}
```

## パッケージを追加する

試しにpandasを追加してみます。

```shell
$ pipenv install pandas
```

すると、それぞれ下記のように追記されます。


```:Pipfile
[packages]
pandas = "*" // バージョン指定なし
```


```json:Pipfile.lock
    "default": {
        "numpy": {
            "hashes": [
                "sha256:03bbde29ac8fba860bb2c53a1525b3604a9b60417855ac3119d89868ec6041c3",
               （略）
                "sha256:f6a7421da632fc01e8a3ecd19c3f7350258d82501a646747664bae9c6a87c731"
            ],
            "version": "==1.18.0"
        },
        "pandas": {
            "hashes": [
                "sha256:00dff3a8e337f5ed7ad295d98a31821d3d0fe7792da82d78d7fd79b89c03ea9d",
               （略）                
                "sha256:ee50c2142cdcf41995655d499a157d0a812fce55c97d9aad13bc1eef837ed36c"
            ],
            "index": "pypi",
            "version": "==0.25.3"
        },
        "python-dateutil": {
            "hashes": [
                "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
                "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
            ],
            "version": "==2.8.1"
        },
        "pytz": {
            "hashes": [
                "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
                "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
            ],
            "version": "==2019.3"
        },
        "six": {
            "hashes": [
                "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
                "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
            ],
            "version": "==1.13.0"
        }
    },
```

numpyやpython-dateutil等、pandasが内部的に依存しているパッケージとそのバージョンも指定されています。

また、バージョンを指定してインストールする場合は下記のようにします。

```shell
$ pipenv install pandas==0.15.0 // 特定のバージョンを指定
```

バージョンの指定方法は特定のバージョンだけでなく、指定のバージョン以上、特定のバージョンを除くといったことが可能です。詳しくは [公式ドキュメント](https://pipenv-ja.readthedocs.io/ja/translate-ja/basics.html#specifying-versions-of-a-package) をご参照ください。

## パッケージを削除する

インストールしたパッケージをアンインストールする場合はuninstallコマンドを使います。

```shell
$ pipenv uninstall pandas
```

そうするとPipfile、Pipfile.lock共にinstallで追記された分が削除されます。

※ 仮想環境からはpandasはアンインストールされますが、pandasが依存していたその他のパッケージはそのままとなります。これらのアンインストールに後述するcleanコマンドを使います。

# 2. lock

Pipfileの内容からPipfile.lockファイルを更新する際はlockコマンドを使います。

```shell
$ pipenv lock
```

特に `pipenv install` 時にバージョンを指定しない場合、パッケージのバージョンアップの際にこのコマンドを使うことになります。

# 3. sync

syncコマンドはPipfile.lockの内容を仮想環境に適用するために利用します。

```shell
$ pipenv sync
```

このコマンドはlockコマンドとセットで利用することが多いと思います。

# 4. update

updateコマンドはlockとsyncを一度に行うものです。

```shell
$ pipenv update        // 全てのパッケージに対して行う
$ pipenv update pandas // パッケージを指定することも可能
``` 

`--dry-run`オブションをつけると、バージョンのアップデートができる（古くなった）パッケージがわかります。

```shell
$ pipenv update --dry-run
✔ Success! 
// 古いバージョン（0.15.0）のpandasを入れたので、新しいバージョン（0.25.3）が利用可能と教えてくれる
Skipped Update of Package pandas: 0.15.0 installed, 0.25.3 available.
```

なお、`pipenv install`時にバージョンを指定している場合、`--dry-run`オプションが無くても新しいバージョンのインストールはスキップされます。その場合は`pipenv install`で任意のバージョンを指定して上書きインストールをします。

# 5. run

仮想環境内でコマンドを実行する場合はrunコマンドを使います。試しにインストールされたパッケージ一覧を表示してみます。

```shell
$ pipenv run pip list
Package         Version
--------------- -------
numpy           1.18.0 
pandas          0.15.0 
pip             19.3.1 
python-dateutil 2.8.1  
pytz            2019.3 
setuptools      43.0.0 
six             1.13.0 
wheel           0.33.6
```

また、Pipfileにスクリプトを登録しておくと、そのスクリプトをrunコマンドで呼び出すことができます。

```:Pipfile
[scripts]
list = "pip list"
```

```shell
$ pipenv run list // pipenv run pip list と同じ結果になる
```

# 6. shell

仮想環境の中に入る時にはshellコマンドを使います。

```shell
$ pipenv shell
```

# 7. clean

仮想環境にはあるがPipfile.lockで定義されていないパッケージがある場合、cleanコマンドでそれらのパッケージを仮想環境から削除できます。

試しに先程出てきた、`pipenv install pandas`の後`pipenv uninstall pandas`を行い、仮想環境ではpandasが依存しているパッケージが残っている状態でcleanコマンドを実行してみます。

```shell
$ pipenv run pip list 
Package         Version
--------------- -------
numpy           1.18.0 // numpy等、pandasが依存しているパッケージが残っている
pip             19.3.1
python-dateutil 2.8.1  
pytz            2019.3 
setuptools      43.0.0 
six             1.13.0 
wheel           0.33.6 
```

```shell
$ pipenv clean // cleanを実行
Uninstalling six…
Uninstalling pytz…
Uninstalling python-dateutil…
Uninstalling numpy…
```

```shell
$ pipenv run pip list // cleanの結果を確認
Package    Version
---------- -------
pip        19.3.1 
setuptools 43.0.0 
wheel      0.33.6 
```

pandasが依存していたパッケージもしっかりアンインストールされているのがわかります。

# 8. check

checkコマンドは、セキュリティの脆弱性をチェックし、現在の環境がPEP 508の要求仕様を満たしているかどうかを調べます。

```shell
$ pipenv check
```

# 9. graph

パッケージの依存関係を見たい場合、graphコマンドを使います。

```shell
$ pipenv graph
pandas==0.15.0 // pandasはnumpy、python-dateutil、pytzに依存していることがわかる
  - numpy [required: >=1.7.0, installed: 1.18.0]
  - python-dateutil [required: >=2, installed: 2.8.1]
    - six [required: >=1.5, installed: 1.13.0] // python-dateutilがさらにsixに依存していることがわかる
  - pytz [required: >=2011k, installed: 2019.3]
```

# まとめ

これでPipenvの全体を俯瞰できたかなと思います。しつこいですが、冒頭の関連図を改めて貼っておきます。

<img width="1365" alt="スクリーンショット 2020-01-01 20.10.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113133/ec6e9d79-229d-da8c-f866-dcf195f160a8.png">

# その他の代替案

良い事ずくめに見えるPipenvですが、dockerと合わせると辛さがあったり、installが遅かったりと、ちらほら難点もあるようです。その他の代替案として下記ツールの名前も聞くので、近いうちに触ってみようと思います。

- Poetry
- Pyflow
- pip-tools