conda パッケージを Juliaから追加しよう



# 付記 2018/3/2
`ENV["CONDA_JL_VERSION"]=3` してね。

→ [Julia] miniconda 3 を導入させるように指定した (= PyPlot が正しく動いた)
https://qiita.com/tenfu2tea/items/917dceb14687ffb1988c


# 元記事

[Julia advent calendar 2016](http://qiita.com/advent-calendar/2016/julialang) 5日目の記事です。

　今年の個人トピックに、mayavi というライブラリが anaconda パッケージで提供されるようになったことがあります。Python でしばらく楽しんでいましたが、Julia からも使いたくなりました。けれど、conda 環境を Julia から使う方法が分かりません。
　conda 環境を Julia から操作する仕組みは、`PyCall.jl` と `Conda.jl` の二つのパッケージが提供しています。これらの機能をユーザ目線で整理してまとめてみました。

# 前提
- MacOS X 10.12.1 Sierra で実行した結果を以下に引用しました。(私のユーザ名は ___ で隠してあります)
- カタカナの「パッケージ」は Julia パッケージを、ひらがなの「ぱっけーじ」は anaconda パッケージをそれぞれ指します。
- カタカナの「モジュール」は Julia モジュールを、ひらがなの「もじゅーる」は Python モジュールをそれぞれ指します。(前者は記事中に出現しませんけれども)

# PyCall 概説

　`PyCall` パッケージは、Julia から Python を呼び出すためのパッケージです。呼び出すだけでなく conda ぱっけーじを追加できます。
　`PyCall` パッケージは、自分でインストールしてなくても、Python を呼び出すパッケージをインストールする際に同時にインストールされることがあります。
　例えば、2次元グラフ描画のパッケージ `PyPlot` をインストールした場合のメッセージを一部抜粋してみましょう。`PyCall` と `Conda` パッケージもインストール (あわせてビルド)されています。

```Julia
julia> Pkg.add("PyPlot")
INFO: Initializing package repository /Users/___/.julia/v0.5
INFO: Cloning METADATA from https://github.com/JuliaLang/METADATA.jl
...
INFO: Cloning cache of Conda from https://github.com/JuliaPy/Conda.jl.git
INFO: Cloning cache of PyCall from https://github.com/JuliaPy/PyCall.jl.git
INFO: Cloning cache of PyPlot from https://github.com/JuliaPy/PyPlot.jl.git
INFO: Installing Conda v0.4.0
INFO: Installing PyCall v1.7.2
INFO: Installing PyPlot v2.2.4
...
INFO: Building Conda
INFO: Building PyCall
```

# PyCall が呼ぶ Python の場所

　`PyCall` を使うには `using PyCall` します。
　`PyCall` で呼び出される python インタプリタのバージョン、絶対パス、ライブラリパスは `PyCall.pyversion`, `PyCall.pyprogramname`,`PyCall.libpython` で、それぞれ得ることができます。

```Julia
julia> Pkg.add("PyCall")  # 一度インストールすれば OK
...
INFO: PyCall is using python (Python 2.7.12) at /Users/___/.julia/v0.5/Conda/deps/usr/bin/python, libpython = /Users/___/.julia/v0.5/Conda/deps/usr/lib/libpython2.7  # PyCallを buildすると Pythonの実行パスとライブラリパスが表示されます

julia> using PyCall

julia> PyCall.pyversion
v"2.7.12"

julia> PyCall.pyprogramname
"/Users/___/.julia/v0.5/Conda/deps/usr/bin/python"

julia> PyCall.libpython
"/Users/___/.julia/v0.5/Conda/deps/usr/lib/libpython2.7"
```

　さて、上の Python は Julia 自身が追加したものです。
　`PyCall` パッケージをビルド `Pkg.build("PyCall")` する際に Python の実行ファイルが見つからないと、miniconda がインストールされます。 例えば、上で例に挙げた `PyPlot` は、`matplotlib` もじゅーるを使います。anaconda を自分で入れなくても `matplotlib` が使えるのは、この仕組みからですね。
　で。この miniconda-python の在処は、上の `PyCall.pyprogramname` が示すディレクトリ、つまり、ホームディレクトリ直下 `.julia`　ディレクトリの下の `Conda` ディレクトリです。

# @pyimport : Python もじゅーるをインポートする

　Python もじゅーるをインポートする (= Julia で使えるようにする)には、`PyCall` パッケージの `@pyimport` マクロを使います。 
　例えば、`matplotlib` もじゅーるを、`PyPlot` を用いずに直に使ってみると、以下のような感じです。

```Julia
julia> using PyCall

julia> @pyimport matplotlib.pyplot as plt

julia> plt.plot( [1,2,3] )

julia> plt.show()

```

　Python と見分けがつきませんが、これは @pyimport マクロが Python 型とJulia 型を相互に変換しているからです。
　Python に渡す引数の型や戻り値の型を細かく指定したい場合には `PyCall.pycall` 命令で pythonの関数を呼び出します。 この辺は、別の解説に譲ります。
　`@pyimport` マクロで、python もじゅーるが見つからないと例外が発生します。
　もう一つ、`pyimport` という命令があります。こちらは、Python もじゅーるをインポートしますが、 Python 型とJulia 型を変換する機能はありません。

# pyimport_conda : conda ぱっけーじの追加

　`PyCall`パッケージの `pyimport_conda()` 命令を用いると、上の miniconda 環境に、Python もじゅーる(ぱっけーじ)を追加できます。
　呼び出し形式は
　`pyimport_conda(modulename, pkg, [channel])` 
です。
　この命令では、最初に、Pythonの ぱっけーじ modulename を探索し、見つからないと pkg ぱっけーじを追加します。その後、上の `pyimport(modulename, pkg, [channel])` を実行し、その戻り値を返します。
　例えば mayavi というもじゅーる (ぱっけーじ名も同じ)を追加するには、以下のようにします。

```Julia
julia> @pyimport mayavi
ERROR: PyError (:PyImport_ImportModule) <type 'exceptions.ImportError'>
ImportError('No module named mayavi',)

julia> pyimport_conda( "mayavi", "mayavi")
```

　`pyimport_conda` は、次に説明する `Conda.add()` 命令を内部で呼び出しています。

# Conda パッケージを用いた conda環境の操作

　`Conda` パッケージを用いて、miniconda 環境を操作できます。シェル (コマンドライン、ターミナル)から使う `conda` コマンドと同様の操作ができます。

- `Conda.list([env])` :  インストールされている conda ぱっけーじ のリストを出力します。
- `Conda.add(pkg, [env])` : conda ぱっけーじ pkg を追加します。
- `Conda.rm(pkg, [env])` : conda ぱっけーじ pkg を削除します。
- `Conda.update([env])` : インストールされている conda ぱっけーじ を最新に更新します。


 実行例を見てみます。

```Julia
julia> Pkg.add("Conda")  # PyCallパッケージをインストールしたら、たいていインストール済炭です。

julia> using Conda

julia> Conda.PYTHONDIR
"/Users/＿＿/.julia/v0.5/Conda/deps/usr/bin"

julia> Conda.list()
# packages in environment at /Users/___/.julia/v0.5/Conda/deps/usr:
#
apptools                  4.2.1                    py27_0
conda                     4.2.13                   py27_0
conda-env                 2.6.0                         0
configobj                 5.0.6                    py27_0
... #　省略

julia> Conda.add("mayavi")
Fetching package metadata .............
Solving package specifications: ..........

Package plan for installation in environment /Users/___/.julia/v0.5/Conda/deps/usr:

The following NEW packages will be INSTALLED:

    mayavi: 4.4.0-np19py27_0

Linking packages ...
[      COMPLETE      ]|###################################################| 100%
... #　省略

julia> Conda.rm("mayavi")
Fetching package metadata .............
Solving package specifications: ..........

Package plan for package removal in environment /Users/___/.julia/v0.5/Conda/deps/usr:

The following packages will be REMOVED:

    mayavi: 4.4.0-np19py27_0

Unlinking packages ...
[      COMPLETE      ]|###################################################| 100%
... #　省略

julia> Conda.update()
Fetching package metadata .............
Solving package specifications: ..........

Package plan for installation in environment /Users/___/.julia/v0.5/Conda/deps/usr:

The following packages will be downloaded:
package                    |            build
---------------------------|-----------------
openssl-1.0.2j             |                0         3.0 MB
... #　省略
```


　以上説明した `Conda.list()`, `Conda.add()`, `Conda.rm()`, `Conda.update()` を含めて、`Conda` の命令は全て `env` という省略可能パラメータをとります。`env` は 操作対象の minionda 環境を指定するもので、既定値は `Conda.PYTHONDIR` (miniconda環境の親ディレクトリのパス)です。

　標準ではない anacondaぱっけーじをインストールする際には、チャネル channel を指定できます。
- `Conda.channels([env])`: conda環境 `env` に含まれるチャネルのリストを出力します。
- `Conda.add_channel(channel, [env])` : conda環境 `env` に、チャネル`channel` を追加します。
- `Conda.rm_channel(channel, [env])`: conda環境 `env` から、チャネル`channel` を削除します。

　既定のチャネルは "defaults" です。

```Julia
julia> Conda.channels()
1-element Array{String,1}:
 "defaults"
```

# Python - Conda 環境の変更

　`PyCall` の呼び出し先をを、自分でインストールした anaconda 環境に変更できます。1. 呼び出す Python パスの修正と、2. conda 環境の参照先の修正の、二つの手順を踏みます。

## 1. 呼び出す Python パスの修正

　`ENV["PYTHON"]` に、Python 実行ファイルのフルパスを指定します。
　次いで、仮想環境 (virtualenv)を用いて anaconda環境を構築した場合は、`rm(Pkg.dir("PyCall","deps","PYTHON"))` を実行します (ここ重要)。
　仕上げに `Pkg.build("PyCall")` で、`PyCall` をビルドします。

```Julia
julia> using PyCall

julia> ENV["PYTHON"]="/anaconda/bin/python"
"/anaconda/bin/python"

julia> rm(Pkg.dir("PyCall","deps","PYTHON"))

julia> Pkg.build("PyCall")
INFO: Building Conda
INFO: Building PyCall
INFO: PyCall is using /anaconda/bin/python (Python 2.7.12) at /anaconda/bin/python, libpython = //anaconda/lib/libpython2.7
```

## 2. conda環境の参照先を変更する

　上では、PyCall で呼ぶ Python の実行パスが変更されただけです。
　conda環境の参照先を変更するには、シェル（ターミナル、コマンドライン）で作業します。
　最初に、Julia から呼び出されるための conda 環境を、新規に作成しておきます。その conda 環境のパスを環境変数 `CONDA_JL_HOME` に設定して `julia -e 'Pkg.build("Conda")’` を実行します。

```shell
$ conda create -n conda_jl python
$ export CONDA_JL_HOME="/Users/___/.pyenv/versions/anaconda-2.4.0/envs/conda_jl"
$ julia -e 'Pkg.build("Conda")'
INFO: Building Conda
```


　以後は、`Conda` を用いて、この conda環境を操作できます。しかし、混乱しそうなので、私は、シェルからぱっけーじを追加・削除することにしています。

```shell
$ conda install mayavi -n conda_jl
Using Anaconda Cloud api site https://api.anaconda.org
Fetching package metadata .............
Solving package specifications: ..........

Package plan for installation in environment /Users/___/.pyenv/versions/anaconda-2.4.0/envs/conda_jl:
... 以下略。
```

## 3. Juliaがインストールした Python に戻す

 Juliaがインストールした conda環境に再び戻してみます。 2. → 1. の順に戻すのが、確実のようです。

(1) condaの参照先を戻して

```shell
$ export CONDA_JL_HOME="/Users/___/.julia/v0.5/Conda/deps/usr"
$ julia -e 'Pkg.build("Conda")'
INFO: Building Conda
```

(2) python 参照先を戻します。

　`ENV["PYTHON"]` に空文字列を指定して `Pkg.build("PyCall")` します。

```julia
julia> ENV["PYTHON"]=""

julia> Pkg.build("PyCall")
INFO: Building Conda
INFO: Building PyCall
INFO: No system-wide Python was found; got the following error:
could not spawn `'' -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('VERSION'))"`: no such file or directory (ENOENT)
using the Python distribution in the Conda package
... 省略

julia> Conda.PYTHONDIR
"/Users/___/.julia/v0.5/Conda/bin"
```

　以上の手順でたいてい conda環境の参照先は元に戻るのですが、戻らない場面も経験しました。最後の手段として "~/.julia/v0.5/Conda" 以下のファイルを削除してから `ENV["PYTHON"]="" ; Pkg.build("Conda")` で、miniconda環境をビルドします (強引？)。



# 予告

　以上で、解説は終わりです。この解説が、所望の conda ぱっけーじを Juliaから使うための参考になれば、うれしいです。
　明日の Julia Advent Calendar も、私の担当です。 本題である mayavi ぱっけーじを Julia から使ってみた例を紹介します。