[翻訳]reticulate vignette: RからPythonへのインターフェース

  • 6
    いいね
  • 0
    コメント

この文書は,RStudio他によるRパッケージreticulate (Version 0.7) のビネット"R interface to Python"の翻訳です.

License: Apache License 2.0


概要

reticulate パッケージはPythonのモジュール,クラス,関数へのRインターフェースを提供します.たとえば次のコードでは,Pythonのosモジュールをインポートして,その中の関数を呼び出しています.

library(reticulate)
os <- import("os")
os$chdir("tests")
os$getcwd()

Pythonのモジュールやクラスの中の関数やその他のデータには,$演算子によってアクセスできます(Rのリストや環境,参照クラスを操作するやり方と同様です).

Pythonを呼び出すときに,Rのデータ型はPythonの同等な型に自動的に変換されます.PythonからRに値が返されるときには,逆にRの型に変換されます.型は以下のように変換されます.

R Python
一要素のベクトル スカラー 1, 1L, TRUE, "foo"
多要素のベクトル リスト c(1.0, 2.0, 3.0), c(1L, 2L, 3L)
複数の型を含むリスト タプル list(1L, TRUE, "foo")
名前付きリスト 辞書 list(a = 1L, b = 2.0), dict(x = x_data)
行列/配列 NumPyの配列(ndarray) matrix(c(1,2,3,4), nrow = 2, ncol = 2)
関数 Pythonの関数 function(x) x + 1
NULL, TRUE, FALSE None, True, False NULL, TRUE, FALSE

カスタムクラスのPythonオブジェクトが返ってくる場合には,Rからそのオブジェクトへの参照が返されます.このオブジェクトに対しては,まるでRの参照クラスのインスタンスであるかのようにして,メソッドを呼び出したり,プロパティにアクセスしたりできます.

reticulate パッケージは,バージョン2.7以降のすべてのPythonで動作します.オプションとしてNumpyが統合できますが,Numpy 1.6以上が必要です.

インストール

reticulateはCRANから以下のようにしてインストールできます.

install.packages("reticulate")

Pythonの場所の特定

使用したいバージョンのPythonがシステムのPATHにある場合には,(Sys.whichによって)自動的にそのPythonが見つかり,使用されます

あるいは,以下の関数のどれかを用いて,他のバージョンのPythonを指定することもできます.

関数 説明
use_python 特定のPythonバイナリへのパスを指定する.
use_virtualenv Pythonのvirtualenvを含むディレクトリを指定する.
use_condaenv condaの環境を指定する.

例:

library(reticulate)
use_python("/usr/local/bin/python")
use_virtualenv("~/myenv")
use_condaenv("myenv")

reticulate でNumpyの機能を使うにはNumpy 1.6以上が必要になるので,この要件を満たすバージョンのPythonの方が望ましいです.

また,デフォルトではuse系関数はPythonがどこで見つかるかのヒントにすぎないということに注意してください(つまり,指定されたバージョンのPythonが存在しなくてもエラーは出ません).指定されたバージョンのPythonが実際に存在することを保証するには,required引数を追加します.

use_virtualenv("~/myenv", required = TRUE)

Pythonは以下の順序で探索されて見つかった場所にあるバージョンが使用されます.

  1. use_pythonuse_virtualenvuse_condaenvを呼び出して指定した場所

  2. RETICULATE_PYTHON環境変数で指定した場所

  3. Sys.which関数によって)システムのPATHで見つかるPythonの場所

  4. その他の慣習的にPythonが配置される場所./usr/local/bin/python/opt/local/bin/python等.

典型的な用途では,Rセッションで最初にimportを呼んだときにPythonの探索とバインディングが実行されます.その結果,importの呼び出しで指定したモジュールを含むバージョンのPythonが優先的に使用されます(すなわち,指定のモジュールを含まないバージョンのPythonはスキップされます).

py_config関数を使うと,使用しているバージョンのPythonや,システムで見つかる他のバージョンのPythonに関する情報を問い合わせることができます.

py_config()

モジュールのインポート

import関数を使って任意のPythonモジュールをインポートすることができます.例を示します.

difflib <- import("difflib")
difflib$ndiff(foo, bar)

filecmp <- import("filecmp")
filecmp$cmp(dir1, dir2)

import_main関数とimport_builtins関数によって,デフォルトでコードが実行される場所であるmainモジュールと,組み込みのPython関数群にアクセスすることができます.例を示します.

main <- import_main()

py <- import_builtins()
py$print('foo')

一般的に,ファイルや文字列からPythonコードを実行して,その結果にアクセスしたい場合にmainモジュールが役立ちます(さらなる詳細は以下の節を見てください).

オブジェクトの変換

PythonのオブジェクトがRに返されるとき,デフォルトではRの同等な型に変換されます.ですが,もしPythonからRへの変換を明示的にし,ネイティブなPythonのオブジェクトを扱うのをデフォルトの設定にしたいのであれば,import関数にconvert=FALSEを渡せばよいです.

# numpyをインポートし,PythonからRへの自動変換を禁止する
np <- import("numpy", convert = FALSE)

# NumPyで配列を操作する
a <- np$array(c(1:4))
sum <- a$cumsum()

# 最後に明示的にRに変換する
py_to_r(sum)

上で示したように,計算の最後にRのオブジェクトへのアクセスが必要であれば,py_to_r関数を明示的に呼び出せばよいです.

コードの実行

py_run_file関数とpy_run_string関数を使って,mainモジュールの中でPythonコードを実行できます.これらの関数は両方ともPythonのmainモジュールへの参照を返すので,実行結果にアクセスすることができます.例を示します.

py_run_file("script.py")

main <- py_run_string("x = 10")
main$x

リスト,タプル,辞書

Rの型からPythonの型への自動変換は大抵の場合にうまくいきますが,Python側が期待している型を与えるためには,R側でより明示的な操作が必要な場合もあります.

たとえば,PythonのAPIがリストを要求しているのにRの一要素のベクトルを渡すと,これはPythonのスカラーに変換されてしまいます.これを克服するには,たんにRのlist関数を明示的に使えばよいです.

foo$bar(indexes = list(42L))

同様に,PythonのAPIがリストではなくタプルを要求することがあるかもしれませんが,その場合にはtuple関数が使えます.

tuple("a", "b", "c")

Rの名前付きリストはPythonの辞書に変換されますが.dict関数を使って明示的にPythonの辞書を作成することもできます.

dict(foo = "bar", index = 42L)

これは,(文字列ではない)より複雑なオブジェクトをキーに持つ辞書を渡す必要がある場合に有用かもしれません.

コンテキスト

Rのwith総称関数を使って,Pythonのコンテキストマネージャオブジェクトを操作できます(Pythonではwithキーワードを使って同じことができます).例を示します.

py <- import_builtins()
with(py$open("output.txt", "w") %as% file, {
  file$write("Hello, there!")
})

この例では,ファイルを開いて,そのファイルがwithブロックの最後に自動的に閉じられることを保証しています.%as%演算子を使って,コンテキストマネージャが作成したオブジェクトに別名を与えていることに注目してください.

イテレータ

もしPythonのAPIがイテレータやジェネレータを返す場合には,iterate関数を使ってこれを操作することができます.iterate関数を使うと,イテレータが返す各要素に対してRの関数を適用できます.

iterate(iter, print)

iterateに関数を渡さないと,結果が集められてRのベクトルになります.

results <- iterate(iter)

iterate関数によってイテレータの値は消費されてしまうことに注意してください.

a <- iterate(iter) # 結果は空でない
b <- iterate(iter) # 要素はすでに消費されてしまったので,結果は空になる

呼び出し可能オブジェクト

Pythonのオブジェクトの中には,メソッドやプロパティへのアクセスだけでなく,呼び出し可能な(つまり普通の関数のように引数を与えて呼び出すことができる)ものがあります.呼び出し可能なPythonオブジェクトは,関数ではなくオブジェクトとしてRに返ってくるものの,$call()メソッドによって呼び出し可能な関数を実行できます.例を示します.

# 呼び出し可能オブジェクトを取得
parser <- spacy$English()
# オブジェクトを関数として呼び出す
parser$call(spacy)

高度な関数

主にPythonライブラリに対して高水準のRインターフェースを作成するときに役立つ,より高度な関数も利用可能です.

Pythonオブジェクト

RからPythonのオブジェクトを操作するには,通常は$演算子を使って必要なオブジェクトの機能にアクセスします.$を使うと,Pythonのオブジェクトは可能であれば自動的にRの同等なオブジェクトに変換されますが,以下の関数を用いればPythonのオブジェクトをより低水準で操作できます(たとえば,明示的にpy_to_rを呼ばない限りRのオブジェクトへの変換は行われません).

関数 説明
py_has_attr オブジェクトが指定した属性を持つか確認する.
py_get_attr Pythonオブジェクトの属性を取得する.
py_set_attr Pythonオブジェクトの属性を設定する.
py_list_attributes Pythonオブジェクトの全属性の一覧を取得する.
py_call 指定した引数でPythonの呼び出し可能オブジェクトを呼び出す.
py_to_r PythonオブジェクトをRの同等なオブジェクトに変換する.
r_to_py RのオブジェクトをPythonの同等なオブジェクトに変換する.

設定

以下の関数を用いると,現在のシステムで利用可能なPythonの設定に関する情報を問い合わせることができます.

関数 説明
py_available Pythonへのインターフェースがこのシステムで利用可能か確認する.
py_numpy_available NumPyへのRインターフェースが利用可能か確認する(要NumPy 1.6以上).
py_module_available Pythonのモジュールがこのシステムで利用可能か確認する.
py_config 使用中のPythonの場所とバージョンの情報を取得する.

出力の制御

以下の関数を用いるとPythonからの出力を捕捉したり抑制したりできます.

関数 説明
py_capture_output 指定した式に対するPythonの出力を捕捉してRの文字ベクトルとして返す.
py_suppress_warnings 指定した式を実行するが,Pythonの警告の表示は抑制する.

その他

以下の関数は,その他の様々な低水準の機能を提供します.

関数 説明
py_unicode 文字列をPythonのユニコードオブジェクトに変換する.
py_str Pythonオブジェクトの文字列表現を取得する.
py_is_null_xptr Pythonオブジェクトがnullなexternalptrであるか確認する.
py_validate_xptr Pythonオブジェクトがnullなexternalptrであるか確認し,そうであればエラーを投げる.

パッケージでの使用

CRANでチェックとテスト

reticulateを他のRパッケージで使うのであれば,次のことを考慮する必要があります.それは,パッケージがCRANに提出される際に,CRANのテスト用サーバにはPythonもNumPyも,あなたがパッケージでラップしようとしている他のPythonモジュールも存在しないかもしれないということです.

あなたのパッケージがCRANでうまく動作することを保証するには,以下の2つのことをやってておくべきです.

  1. パッケージの中で使うPythonモジュールをインポートする際には,モジュール(とPython)がそれらを最初に使うときにだけロードされることを保証するために,delay_loadオプションを使うべきです.

    # 自分のパッケージで使用したいpythonの'foo'モジュール
    foo <- NULL
    
    .onLoad <- function(libname, pkgname) {
      # fooモジュールを遅延ロードする($でアクセスするときにのみロードされる)
      foo <<- import("foo", delay_load = TRUE)
    }
    
  1. テストを書く際には,モジュールが利用可能かどうか確認し,利用不可能であればテストをスキップするようにします.たとえばtestthatパッケージを使っているのであれば,以下のようになるでしょう.

    # 'foo'モジュールがなければテストをスキップするためのヘルパ関数
    skip_if_no_foo <- function() {
      have_foo <- py_module_available("foo")
      if (!have_foo)
        skip("テストにfooが利用できません!")
    }
    
    # このヘルパ関数をすべてのテストで呼ぶようにする
    test_that("期待通りに動く", {
      skip_if_no_foo()
      # ここにテストコードを書く
    })
    

S3メソッド

reticulate によってR側に露出しているPythonオブジェクトのクラスはRにも持ち越されるので,クラスに対してS3メソッドを書いて,たとえばstrprintの動作をカスタマイズすることができます(ただし通常はそうする必要はありません.デフォルトのstrメソッドやprintメソッドはPyObject_Strを呼び出しますが,これが普通は許容可能なデフォルトの動作を提供するからです.

もし本当にPythonのクラス向けにカスタマイズしたS3メソッドを実装すると決めたのであれば,次のことに留意するのが大切です.すなわち,Rセッションの終了時にはPythonオブジェクトへのコネクションが失われるので,あるRセッションで保存した.RDataを後続のRセッションでリストアすると,Pythonオブジェクトが実質的に失われてしまうということです(正確に言うと,RのNULLexternalptrオブジェクトになってしまいます).

これが意味するところは,S3メソッドでPythonオブジェクトを操作する前には,常にpy_is_null_xptrを使うべきだということです.例を示します.

#' @export
summary.MyPythonClass <- function(object, ...) {
  if (py_is_null_xptr(object))
    stop("Object is NULL")
  else
    # オブジェクトを操作してサマリを生成する
}

これをより簡単に行うために,いくつかショートカット用メソッドが用意されています.py_validate_xptr関数は必要な確認を行い,失敗したら自動的にエラーを投げます.したがって上の例は次のように書き直すことができるでしょう.

#' @export
summary.MyPythonClass <- function(object, ...) {
  py_validate_xptr(object)
  # オブジェクトを操作してサマリを生成する
}

最後になりますが,reticulateパッケージはpy_str総称関数をエクスポートしており,そのメソッドはstrメソッドから適切な検証(オブジェクトがNULLなら<pointer: 0x0>を返す)を経た後にのみ呼び出されます1.以下のようにしてpy_strのメソッドを実装できます.

#' @importFrom reticulate py_str
#' @export 
py_str.MyPythonClass <- function(object, ...) {
  # オブジェクトを操作して文字列を生成する
}

要するに,カスタムのstrメソッドやprintメソッドを提供するにはpy_strを実装してください.その他のS3メソッドについては,オブジェクトを操作する前に必ずpy_validate_xptrpy_is_null_xptrを呼ぶようにしてください.


  1. [訳注]若干わかりにくいように思うので補足する.まずreticulateによって,任意のPythonオブジェクトはR側ではpython.builtin.objectクラスを継承したオブジェクトとして見える.python.builtin.objectに対してはstrメソッドが定義されており,このメソッドがpy_str総称関数を呼び出す.そしてpy_str総称関数は引数のチェックをした後にメソッドを呼ぶという構造になっている. 

    reticulate:::str.python.builtin.object
    
    ## function (object, ...) 
    ## {
    ##     cat(py_str(object), "\n", sep = "")
    ## }
    ## <environment: namespace:reticulate>
    
    reticulate::py_str
    
    ## function (object, ...) 
    ## {
    ##     if (!inherits(object, "python.builtin.object")) 
    ##         py_str.default(object)
    ##     else if (py_is_null_xptr(object) || !py_available()) 
    ##         "<pointer: 0x0>"
    ##     else UseMethod("py_str")
    ## }
    ## <environment: namespace:reticulate>