Julia
Anaconda
PyCall
JuliaDay 5

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

Julia advent calendar 2016 5日目の記事です。

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

前提

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

PyCall 概説

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

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> 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> 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> @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> 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> 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> 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")’ を実行します。

$ 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環境を操作できます。しかし、混乱しそうなので、私は、シェルからぱっけーじを追加・削除することにしています。

$ 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の参照先を戻して

$ 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> 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 から使ってみた例を紹介します。

付記 2018/3/2

ENV["CONDA_JL_VERSION"]=3 してね。

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