3 PyCallを使ってみる(1)
ではPyCallを実際に使ってみましょう。はじめに本稿執筆時点でのインストール方法を説明します。それから、前説で紹介したPyCallの基本機能を試します。最後に、Pythonのデータ分析用ライブラリをPyCall経由で利用して実際のデータを分析します。
3.2 準備
3.2.1 PyCallのインストール
PyCallはgem install
コマンドでインストールできます。まだ安定版をリリースしていないため、gem install
コマンドに--pre
オプションを指定する必要があります。PyCallと同時にmatplotlibのラッパーライブラリもインストールしてください。
$ gem install --pre pycall matplotlib
3.2.3 Pythonのインストール
次にPythonをインストールしましょう。本稿ではPython 3.6.0で動作確認をしています。注意点はただ一つ、共有ライブラリlibpythonをインスールすることです。
Linuxの場合、apt-get
コマンドやyum
コマンドを用いてPythonをインストールすると共有ライブラリが有効なものがインストールされます。
Anacondaを利用してインストール[7]する場合も、共有ライブラリが有効なPythonがインストールされます。MacOS XやWindowsを利用している方は、Anacondaを利用してPythonをインストールすると良いでしょう。
pyenv
[8]を利用したり、自分でソースツリーを展開してビルド[9]したりする場合は、configure
に--enable-shared
オプションを指定することを忘れないでください。
3.2.8 Pythonのライブラリ群のインストール
Pythonのインストールが完了したら、次はライブラリ群をインストールしましょう。ライブラリをインストールするためにpip
というパッケージ管理システムが必要になります。こちらは次のコマンドでインストールできます。
$ curl -kL https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python
Linuxでapt-get
やyum
を用いてPythonをインストールした場合は、パイプの後ろのpython
をsudo python
に置き換える必要があります。
pip
をインストールできたら、必要なライブラリをインストールしましょう。本稿では次のライブラリを利用します。
$ pip install matplotlib numpy pandas seaborn
3.3 基本機能の確認
第2節で解説したPyCallの基本機能を実際に試してみましょう。
3.3.2 PyCall.evalと型変換
PyCall.evalを利用して、Pythonコードの評価結果がRubyではどういったオブジェクトに変換されるかを見ていきましょう。次のように、Pythonの整数、浮動小数点数、複素数は、それぞれRubyのInteger
、Float
、Complex
に変換されます。
PyCall.eval('1').class #=> Integer PyCall.eval('1.1').class #=> Float PyCall.eval('1 + 1j').class #=> Complex
また、次のようにPythonの文字列はRubyのString
に変換されます。
PyCall.eval('"string"').class #=> String
list
はPyCall::List
オブジェクトでラップされ、要素の取り出しなどの基本機能はRubyの配列と同様の自然な記法でサポートされます。
list = PyCall.eval('[1, 2, 3]') list.class #=> PyCall::List list.length #=> 3 list[0] #=> 1 list[1] #=> 2 list[3] #=> 3
dict
はPyCall::Dict
オブジェクトでラップされます。PyCall::Dict
はHash
と似た振る舞いになるように作られています。
dict = PyCall.eval('{ "a": 1, "b": 2, "c": 3 }') dict.length #=> 3 dict['a'] #=> 1 dict['b'] #=> 2 dict['c'] #=> 3 dict.keys #=> [ 'a', 'b', 'c' ] dict.values #=> [ 1, 2, 3 ]
さらに、tuple
はPyCall::Tuple
で、set
はPyCall::Set
でラップされます。それぞれlist
やdict
と同様に確認できるのでやってみてください。
また、クラス対応表に登録されていないクラスのインスタンスがPyCall::PyObject
に変換されることを確認してみましょう。
PyCall.eval(<<PYTHON, input_type: :file) class Hoge: def ping(self, str): return "PONG: " + str PYTHON hoge = PyCall.eval('Hoge()') hoge.class #=> PyCall::PyObject
変数hoge
が参照するオブジェクトを使い、インスタンスメソッドへのアクセスが関数オブジェクトの取り出しになること、そしてインスタンスメソッドを呼び出すためにはmethod_name.(args, ...)
記法を利用する必要があることを確認してみましょう。
hoge.ping #=> PyCall::PyObject hoge.ping.inspect #=> "<bound method Hoge.fun of …>" hoge.ping.("hello") #=> "PONG: hello"
このように、ただhoge.ping
と書いてもメソッドのオブジェクトが取り出せるだけあり、それを呼び出すためにhoge.ping.(...)
と書く必要があります。
次はラッパークラスを作り、インスタンスメソッドを自然に呼び出せるようにしてみましょう。Ruby側にもHoge
クラスを作り、PyCall::PyObjectWrapper
を利用してラッパークラスにします。
class Hoge include PyCall::PyObjectWrapper wrap_class PyCall.eval('Hoge') end
これだけでラッパークラスの定義とクラス対応表への登録が完了しています。簡単ですね。
では、PyCall.eval
で再度Python側のHoge
クラスのオブジェクトを作ってみましょう。
hoge2 = PyCall.eval('Hoge()') hoge2.class #=> Hoge
このように、wrap_class
でラッパークラスを定義すると、Ruby側のクラスがPyCall::PyObject
からHoge
に変わりました。Hoge#ping
メソッドをRubyの自然な記法で呼び出せるように上書きしてみましょう。
class Hoge def ping(*args) super().(*args) end end
これで変数hoge2
が参照するオブジェクトのping
メソッドをmethod_name.(args, ...)
記法を使わずに呼び出せるようになっています。実際に呼び出してみましょう。
hoge2.ping "hi" #=> "PONG: hi"
はい、呼び出せてますね。
このようなラッパーメソッドを手作業で作っていくのは面倒です。そのため、ラッパーメソッドを定義しやすくするヘルパーをPyCallの安定版のリリースまでに追加する予定です。
3.3.3 pyimportとpyfrom
続いてPythonのモジュールをRubyのモジュールにインポートしてみましょう。
require 'pycall/import' module Py extend PyCall::Import pyimport :numpy, as: :np end
これを評価すると、モジュールPy
が定義され、Pythonのnumpy
モジュールをラップしたPyCall::PyObject
オブジェクトを返す特異メソッドnp
がモジュールPy
に追加されます。Py.np
がnumpy
であることを確かめてみましょう。
Py.np #=> <module 'numpy' from ...> Py.np.arange.(0, 10) #=> array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Py.np
がnumpy
モジュールであることが確認できました。np.arange
を呼び出すためにmethod_name.(args, ...)
記法を使う必要があるのは上述の通りです。
3.3.5 PyCall.wrap_ruby_callable
PyCall.wrap_ruby_callable
の例として、sorted
関数のkey
にRubyのProcオブジェクトを渡す例を示します。
次の例は、Pythonのsorted
関数を使い、1から9までの整数を持つ配列、偶数は符号を変えて負数として、奇数はそのままの値で比較して並び替えます。実行結果は[8, 6, 4, 2, 1, 3, 5, 7, 9]
というリストになりますが、並び替えの基準となるキー関数としてRubyのProcオブジェクトを渡している点に注目してください。
ary = [1, 2, 3, 4, 5, 6, 7, 8, 9] key_func = ->(v) { v.even? ? -v : v } sorted_list = PyCall.builtin.sorted.(ary, key: PyCall.wrap_ruby_callable(key_func)) p sorted_list #=> [8, 6, 4, 2, 1, 3, 5, 7, 9]
上述の通り、PyCallはProcオブジェクトに対しては自動的にPyCall.wrap_ruby_callable
を使ってラッパーを生成するので、この例の3行目は以下の通りに書いても動きます。
sorted_list = PyCall.builtin.sorted.(ary, key: key_func)