Pipenvを使ってみた


# はじめに

私が`Pipenv`というものを知って、使い始めてから半年くらいが経ったので、
便利だな〜って思ったところをまとめていきます。

簡単に言うと`Pipenv`便利だからみんなも使ってみてねって記事です。

# これまで

`Pipenv`の存在を知るまでは`venv`を使って仮想環境を作り、`requirements.txt`に記載されたモジュールを`pip`でインストールする形で管理していました。

``` :requirements.txt
requests==2.22.0
```

こんなファイルを準備して

``` console
$ python3 -m venv venv
$ ./venv/bin/python -m pip install -r requirements.txt
Collecting requests==2.22.0 (from -r requirements.txt (line 1))
  Using cached https://files.pythonhosted.org/packages/51/bd/23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/requests-2.22.0-py2.py3-none-any.whl
Collecting idna<2.9,>=2.5 (from requests==2.22.0->-r requirements.txt (line 1))
  Using cached https://files.pythonhosted.org/packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests==2.22.0->-r requirements.txt (line 1))
  Using cached https://files.pythonhosted.org/packages/b9/63/df50cac98ea0d5b006c55a399c3bf1db9da7b5a24de7890bc9cfd5dd9e99/certifi-2019.11.28-py2.py3-none-any.whl
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 (from requests==2.22.0->-r requirements.txt (line 1))
  Using cached https://files.pythonhosted.org/packages/b4/40/a9837291310ee1ccc242ceb6ebfd9eb21539649f193a7c8c86ba15b98539/urllib3-1.25.7-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests==2.22.0->-r requirements.txt (line 1))
  Using cached https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl
Installing collected packages: idna, certifi, urllib3, chardet, requests
Successfully installed certifi-2019.11.28 chardet-3.0.4 idna-2.8 requests-2.22.0 urllib3-1.25.7
```

こんな感じですね。

この環境でコード実行する場合は`source ./venv/bin/activate`するか、`./venv/bin/python`を直接指定するかです。


# これまでの問題点

これまでのモジュールの管理のしかただといくつか問題がありました。

## 依存しているモジュールがわかりづらい

``` :requirements.txt
requests==2.22.0
```

最初、この`requirements.txt`の書き方だと、依存しているモジュールのバージョンが固定出来ない事が気になりました。
しかし、`pip`コマンドには`freeze`というサブコマンドがあり、現在インストールされているすべてのモジュールを`requirements.txt`に記述出来る形式で出力してくれます。

その結果私の`requirements.txt`はこうなりました。

``` :requirements.txt
certifi==2019.11.28
chardet==3.0.4
idna==2.8
pkg-resources==0.0.0
requests==2.22.0
urllib3==1.25.7
```

これで依存モジュールまですべてバージョンが指定出来ます！ただここで大きな問題が、

**モジュールの依存関係が全くわからない**

後述しますが、この点でわたしは`Pipenv`が手放せない体になりました。

現状の`requirements.txt`ではインストールされるすべてのモジュールを列挙しただけなので、それぞれの依存関係がまったくわかりません。
またプロジェクトによっては`dev-requirements.txt`や`build-requirements.txt`にファイルが別れていたりして(私の開発してるプロジェクトがそうだった)、
モジュールのバージョンひとつあげようとしても一苦労。どれが依存しているパッケージだか分からないので、一旦別環境にインストールしてみて依存パッケージを確認したりしていました。

## コード実行までが面倒くさい

仮想環境を作ってコードを実行するまで、私は以下のような手順を実行していました。

``` console
$ python3 -m venv venv  # 仮想環境作成
$ ./venv/bin/pip install -U setuptools pip wheel  # 基本モジュールを最新版にする
$ ./venv/bin/pip install -r requirements.txt  # 必要なモジュールのインストール
$ ./venv/bin/python main.py  # 仮想環境でコードの実行
```

なーがいっ！一度作成したvenv環境をとっておけばいいんだろうけどそうもいかないものなのです。

### Makefileを使えば気にならない

私は`Makefile`を利用してこれをコマンドひとつで出来るようにしています。

``` Make:Makefile
venv:
  python3 -m venv venv
  ./venv/bin/pip install -U setuptools pip wheel
  ./venv/bin/pip install -r requirements.txt

.PHONY: run
run:
  ${MAKE} venv
  ./venv/bin/python main.py
```

これで`make run`でいつでもコードが実行出来るようになります。

# Pipenvを知ってしまった

ある日`Pipenv`に出会った。。。

https://pipenv-ja.readthedocs.io/ja/translate-ja/

`Pipenv`は公式のドキュメントがすごく良く出来てます。日本語だし読みやすい。
個人的にはこれがすごく助かりました。

# Pipenvのよいところ

`Pipenv`の機能自体は公式ドキュメントをみてもらえばわかると思います。
ここでは、`Pipenv`を使ってみて今までの管理方法より、
便利になったと感じた機能をいくつか紹介していきます。

## モジュールの依存関係がわかりやすい

これを言いたくてこの記事を書いたと言っても過言ではありません。

ここでは深く言及しませんが、`Pipenv`では`requirements.txt`の代わりに`Pipfile`というファイルでモジュールを管理します。
このファイルは`pipenv`コマンド実行時に自動で生成されるので、直接編集する機会は少ないかもしれません。

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

[dev-packages]

[packages]
requests = "==2.22.0"

[requires]
python_version = "3.6"
```

ここで`pipenv graph`というコマンドを実行してみます。

``` console
$ pipenv graph
requests==2.22.0
  - certifi [required: >=2017.4.17, installed: 2019.11.28]
  - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
  - idna [required: >=2.5,<2.9, installed: 2.8]
  - urllib3 [required: >=1.21.1,<1.26,!=1.25.1,!=1.25.0, installed: 1.25.7]
```

**インストールされているモジュールが確認出来るのはもちろん、それぞれの依存関係まで一目瞭然。**
これが個人的に一番ありがたかったです。

モジュールごとに求められているバージョンまでわかるので、バージョンを変えたいときなどにいちいち依存関係を確認する必要がなくなります。

## コード実行までが簡単

`Pipenv`を利用していると仮想環境の作成→必要なモジュールのインストール→仮想環境でコード実行まで以下のコマンドで出来ます。

``` console
$ pipenv install  # 仮想環境の作成+必要なモジュールのインストール
$ pipenv run python main.py   # 仮想環境でmain.pyを実行
```

[コード実行までが面倒くさい](#コード実行までが面倒くさい) で紹介した手順と比べると明らかにコマンドの数が減っています。
このくらいの量であればわざわざ`Makefile`を使う必要もなくなります。

## scriptsを定義出来る

`Pipfile`内に`scripts`というセクションを定義することで、よく実行する文をコマンドとして定義出来ます。
詳細は[こちら](https://pipenv-ja.readthedocs.io/ja/translate-ja/advanced.html#custom-script-shortcuts) (公式)

例えばテストを実行するコマンドを定義した`Pipfile`が以下のようになります。

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

[dev-packages]
pytest = "==5.3.1"

[packages]
requests = "==2.22.0"

[requires]
python_version = "3.6"

[scripts]
test = "python -m pytest tests/"
```

`[scripts]`というセクションが追記されています。

これは`pipenv run`コマンドに続くサブコマンドを定義でき、この場合`pipenv run test`で`pytest`を使ったテストが実行されるようになっています。
testの実行や、パッケージングなど、よく使うコマンドは`Makefile`に記述していたのですが、これを使えば簡単なコードを実行する程度であれば`Pipfile`ひとつで済みます。

## .envファイルを読み込んでくれる

`pipenv run`でコードを実行する時、`.env`ファイルを自動で読み込んでくれます。
詳細は[こちら](https://pipenv-ja.readthedocs.io/ja/translate-ja/advanced.html#automatic-loading-of-env) (公式)

これも地味にありがたい。

自分はこの機能は、設定ファイルのような使い方をしています。
`.env`ファイルに`key=value`の形式で値を記述し、コード内では環境変数から値を読み取るようにします。

``` :.env
KEY=VALUE
```

``` Python:main.py
import os
key = os.environ.get("KEY")
print(key)  # VALUE
```

これだけで設定ファイルのように、`.env`内のファイルを編集するだけで実行結果が変わります。

``` console
$ pipenv run python main.py
VALUE
```

コードの規模が大きくなればちゃんと設定ファイルを読み込む仕組みを作ったほうがいいですが、
小規模なコードではいちいち`ConfigParser`などを利用して設定ファイルを読み込むコードを書くのは手間なので、この機能はすごく助かってます。

# Pipenv使ってみてね

`Python`を使っている人で`Pipenv`初めて聞いたよって方々、ぜひ使ってみてください。