SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Ruby-Pythonブリッジライブラリ「PyCall」を使ってRubyでデータ分析をしよう!

  • X ポスト
  • このエントリーをはてなブックマークに追加

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-getyumを用いてPythonをインストールした場合は、パイプの後ろのpythonsudo pythonに置き換える必要があります。

 pipをインストールできたら、必要なライブラリをインストールしましょう。本稿では次のライブラリを利用します。

$ pip install matplotlib numpy pandas seaborn

3.3 基本機能の確認

 第2節で解説したPyCallの基本機能を実際に試してみましょう。

3.3.2 PyCall.evalと型変換

 PyCall.evalを利用して、Pythonコードの評価結果がRubyではどういったオブジェクトに変換されるかを見ていきましょう。次のように、Pythonの整数、浮動小数点数、複素数は、それぞれRubyのIntegerFloatComplexに変換されます。

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

 listPyCall::Listオブジェクトでラップされ、要素の取り出しなどの基本機能はRubyの配列と同様の自然な記法でサポートされます。

list = PyCall.eval('[1, 2, 3]')
list.class #=> PyCall::List
list.length #=> 3
list[0] #=> 1
list[1] #=> 2
list[3] #=> 3

 dictPyCall::Dictオブジェクトでラップされます。PyCall::DictHashと似た振る舞いになるように作られています。

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 ]

 さらに、tuplePyCall::Tupleで、setPyCall::Setでラップされます。それぞれlistdictと同様に確認できるのでやってみてください。

 また、クラス対応表に登録されていないクラスのインスタンスが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.npnumpyであることを確かめてみましょう。

Py.np #=> <module 'numpy' from ...>
Py.np.arange.(0, 10) #=> array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

 Py.npnumpyモジュールであることが確認できました。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)

次のページ
3 PyCallを使ってみる(2)

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

村田 賢太(株式会社Speee)(ムラタ ケンタ)

 Kondara MNU/Linuxの開発をはじめ、いくつかのオープンソースソフトウェアの開発への参画を経て、2010年よりCRubyのコミッターとして、主にbigdecimalライブラリのメンテナンスを担当する。クックパッド株式会社(2011〜2016)。株式会社リクルートホールディングス(201...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/10251 2017/07/14 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング