Rubyist が pyenv を使うときに知っておいてほしいこと

## はじめに

機械学習ブームなどにより、 Python を触り始める Rubyist が増えてきたと思います。その際に問題になりやすいのが環境構築です。Rubyだと rbenv がデファクトスタンダードになっているのに、なぜか Python には pyenv に否定的な意見が多いんですよね。

私は pyenv を使っていますし、便利だと思っています。また、 Ruby は殆ど使わないのですが、RubyとPythonのツールスタックの違いについても調べました。 (参考: [gem, bundler と pip, venv の比較](http://qiita.com/methane/items/570728ad3e79c74f4e9e))

その視点から、 Rubyユーザーが自分でpyenvの使い方を自分で決める上で知っておいた方が良いだろうなと思う Ruby と Python の環境の違いをまとめてみます。

## tl;dr

丁寧に解説しても、「Python使うにはこんな長い記事を読まないといけないの」とすぐに否定的に読む人が居るので、4行でまとめます。

1. Ruby で言う Bundler を使わない用途では、 pyenv と rbenv はほぼ同じ
2. Bundler を使うような場面では Python では venv を使うけど、 venv は Python のバージョンも固定するから ``pyenv local`` 要らない
3. pyenv-virtualenv を使うと便利になるけど、 Bundler を rbenv で管理する様なものだから shims が汚れる
4. 逆に shims を使わず、シェルを重くせずに、 pyenv をビルドにだけ使う使い方もアリ

## Bundler と venv の違い

Ruby と Python ではパッケージ管理ツールの役割分担が少し異なります。 Python の pip は依存性解決やバージョンロックなどの機能を持っているので、その点では gem だけでなく Bundler の役割の一部を内包しています。

一方で、プロジェクトごとにパッケージを独立して管理する機能 (Bundler の ``bundle install --path``) は [venv](http://docs.python.jp/3/library/venv.html) というツールを使います。[^1] (参考: [bundler の --path と venv](http://qiita.com/methane/items/570728ad3e79c74f4e9e#bundler-%E3%81%AE---path-%E3%81%A8-venv))

venv と ``bundle install --path`` の大きな違いとして、 venv は単にパッケージだけを格納するのではなく、独立してセットアップされた Python の様に振る舞います。これを「仮想環境」と呼んでいます。仮想環境は標準ライブラリは元になった Python のものをそのまま流用しているので、本物のインストールされたPython環境よりはコンパクトです。次の例を見てください。

```console
$ python3 -m venv .venv  # .venv という名前のディレクトリに仮想環境を作る。

$ ls -F .venv/
bin/                include/            lib/                pip-selfcheck.json  pyvenv.cfg

$ .venv/bin/pip install tornado
Collecting tornado
Installing collected packages: tornado
Successfully installed tornado-4.5.1

$ .venv/bin/python
Python 3.6.1 (default, May 18 2017, 16:23:51)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import tornado
```

ここから分かるように、仮想環境では普通にPythonにライブラリをインストールするときのように ``pip install`` をオプションなしで呼び出し、 ``bundle exec ruby`` のようなことをしなくても仮想環境内の python はインストールしたライブラリを import できるようになっています。

さて、 Ruby で bundle exec するときとの大きな違いとして、仮想環境の Python バージョンは、仮想環境を作るときに決まるので、どのバージョンの Python を使うかは仮想環境を作るときに一度だけ指定できれば十分です。つまり、 ``pyenv local`` で毎回同じバージョンの Python を使うように設定する必要はありません。[^2]

[^2]: この違いが、「シェルに魔術を持ちこんで遅くするほどのメリットがあるか」について pyenv に否定的な意見が多い理由だと思います。

仮想環境を作るときだけ、一時的に Python のバージョンを指定するには次のような方法があります。

```console
$ pyenv shell 3.6.1
$ python -m venv venv
$ pyenv shell --unset
(あるいは)
$ `pyenv root`/versions/3.6.1/bin/python -m venv venv
```

[^1]: ``pip install --target`` あるいは ``pip install --prefix`` オプションがあるのですが、 ``bundle exec`` 相当のコマンドがなく、 ``PYTHONPATH`` を自前で設定する必要があります。あまり一般的なやり方では無いのでおすすめしません。

### 仮想環境の activate について

venv で作った仮想環境は "activate" することができます。 "activate" すると環境変数 PATH の先頭にその仮想環境の bin ディレクトリが追加されるので、 `venv/bin/pip` の代わりに単に `pip` の様にコマンドを実行することができます。

また環境変数 VIRTUAL_ENV も設定されます。vim の Python 用補完プラグインなどがこの環境変数をみて import しているライブラリのメソッド補完を行ってくれたりします。

```
$ . venv/bin/activate  # 仮想環境を activate する

$ echo $VIRTUAL_ENV
/Users/inada-n/tmp/venv

$ which python
/Users/inada-n/tmp/venv/bin/python

$ which pip
/Users/inada-n/tmp/venv/bin/pip

$ deactivate  # activate を解除するコマンド

$ which python
/Users/inada-n/pyenv/shims/python
```


## [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) について

pyenv-virtualenv は pyenv の機能を使って仮想環境を管理するツールです。次のようなメリットがあります。

1. 標準ライブラリに venv を持ってない Python 2 にも virtualenv というサードパーティツールで対応
2. 仮想環境のディレクトリを pyenv 内で管理してくれる
3. ``pyenv local`` などの仕組みで仮想環境を利用できる (activate してくれる)

しかし、この3番について注意があります。 rbenv, pyenv などは shims という仕組みで複数バージョン (仮想環境を含む) の使い分けを実現しています。

shims についてざっくり説明すると、全てのバージョンから `bin/` ディレクトリ配下のコマンド名を集め、 `$PYENV_ROOT/shims` の中に同名のシェルスクリプトを用意します。そのシェルスクリプトは `pyenv exec コマンド名` を実行するようになっています。

この仕組で仮想環境を管理するということは、仮想環境内のコマンド、Bundlerでいえば ``bundle exec コマンド名`` で実行するようなコマンドも shims に入り、 PATH でアクセスできる状態になるということです。あるプロジェクト用の仮想環境でしかインストールしていないライブラリが提供するコマンドも、常に PATH 内に存在し、TABキー補完に現れ、実行したらエラー
 (``pyenv: pygmentize: command not found`` など) になります。

つまり、 pyenv-virtualenv の便利さは、「隔離環境のコマンドがグローバルのPATHを汚す」というデメリットと引き換えになります。
とは言え、 ``pyenv uninstall 仮想環境名`` したらその仮想環境にしかないコマンドは shims から削除されるので、デメリットが実際に問題になってから pyenv-virtualenv を徐々に止めていくことは十分可能なのでそこまで怖がる必要はないと思います。


## pyenv-virtualenv の代替手段

pyenv-virtualenv を使わない場合に、代替として使えるツールを簡単に紹介しておきます。個々のツールの具体的な使い方は別途検索してください。

### [direnv](https://direnv.net/)

direnv はプロジェクトのディレクトリに入ったり出たりしたときに環境変数をカスタマイズすることができるツールです。

pyenv-virtualenv の提供する「仮想環境の管理」と「自動activate」という機能のうち、後者を代替できます。例えば次のように `.envrc` に書けば、自動でそのディレクトリの中の `venv` という仮想環境を activate してくれます。

```bash:.envrc
source venv/bin/activate
```

Python にかぎらず、例えば Go の開発者だったらプロジェクト毎に GOPATH を変更するなど、汎用的に使えるのが便利です。

### venv を直接使う

後述する virtualenvwrapper や pew などの仮想環境管理ツールを使わず、上（「Bundler と venv の違い」）で紹介した手順で venv を直接使うというのが最初の候補になります。

これが最も rbenv + Bundler の使い勝手に近いので分かりやすいと思います。仮想環境の自動 activate が必要であれば上で紹介した direnv を使えます。

Bundler に比べて残念なのが、 `vendor/bundle` や `node_modules` のような仮想環境ディレクトリ名の標準やデファクト・スタンダードが存在しないことです。 `venv`, `.venv`, `vendor/venv` など、どれを使うかを自分またはチームで決める必要があります。

### [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/)

virtualenvwrapper は昔から Python のヘビーな開発者に使われていた仮想環境管理ツールです。

`WORKON_HOME` という環境変数で指定したディレクトリ (指定されなかった場合は ``$HOME/.virtualenvs``) に仮想環境を作ったり消したりactivateしたりできます。

### [pew](https://github.com/berdario/pew)

pew は virtualenvwrapper に似ていますが、仮想環境の activate に venv が提供しているコマンドではなく独自実装を利用しています。具体的には、現在のシェルをカスタマイズするのではなく、カスタマイズされたシェルを子プロセスとして実行します。 ``deactivate`` コマンドの代わりに Ctrl-D で子シェルを閉じれば元の環境に戻ります。

なお、 pew をインストールすると依存として pythonz という、 pyenv と競合するような複数バージョンPythonをビルド、管理するツールがインストールされますが、 pyenv に比べて更新が遅いので気にせず pyenv を使えば良いと思います。

### [pipenv](http://docs.pipenv.org/en/latest/)

Bundler のような使い勝手の新しいツールです。 pip が利用する requirements ファイルの代わりに、より高機能な TOML ベースの Pipfile というファイルを使ってライブラリを管理します。 なお Pipfile はまだ experimental 段階のツールで、今後フォーマットが変わる可能性もあるので、人柱覚悟が必要です。 (そんなに頻繁には変わらないと思いますが)

デフォルトでは仮想環境の管理は pew を利用していますが、 ``PIPENV_VENV_IN_PROJECT=true`` と環境変数に設定しておけばプロジェクト配下の `.venv` ディレクトリを利用するようになるので、より Bundler の使い勝手に近づくと思います。

## pyenv を Python をビルドするためだけに利用する方法

``pyenv local`` などの pyenv のバージョン切り替え機能を使わない場合、 [pyenv のインストール手順](https://github.com/pyenv/pyenv#basic-github-checkout) にある ``echo 'eval "$(pyenv init -)"' >> ~/.bash_profile`` を省略すると、シェルを軽量化し、 shims も利用しないようにする事ができます。 この手順を省いても ``pyenv install``, ``pyenv uninstall``, ``pyenv versions`` は問題なく動きます。

``pyenv global`` の代わりに、普段利用する Python バージョンに対して直接 PATH を通してやると良いでしょう。 

```
$ echo 'export PATH=$PYENV_ROOT/versions/3.6.1/bin:${PATH}' >> ~/.bash_profile
```

潔癖症なら、普段利用する Python を仮想環境にして、 pyenv がビルドした Python をクリーンに保つこともできます。

```
$ $PYENV_ROOT/versions/3.6.1/bin/python -m venv ~/local/python-default
$ echo 'export PATH=$HOME/local/python-default/bin:${PATH}' >> ~/.bash_profile
```

----