PythonからRustを呼び出すために,PyO3を使用します.
環境
- Windows 10 Pro 64bit
- CPU : Core i3-7100U CPU @ 2.40GHz
- Memory : 12.0 GB
- rustc 1.42.0-nightly (2020-01-17)
- cargo 1.42.0-nightly (2020-01-13)
- Python 3.8.1 for windows 10 64bit
RustでPython用のClassを作成
Cargo.toml
PyO3の説明の通りに作成します.
[package] name = "pyt" version = "0.1.0" authors = ["atuhiro-me.net"] edition = "2018" [lib] name = "pyt" crate-type = ["cdylib"] [dependencies.pyo3] version = "0.8.5" features = ["extension-module"]
pytというモジュールに,PyTというクラスを作成します.外部から呼び出せるようにします.PyTクラスのコンストラクタを作成するときは,#[new]とし,new(&PyRawObject)を定義します.&PyRawObject のinit()で,必要な構造体のメモリを確保します.戻り値は不要です.
PyTにnumというメンバを作成し,get()で数値を取得できるようにします.
lib.rs
use pyo3::prelude::*; #[pyclass(module = "pyt")] struct PyT { num: i32, } #[pymethods] impl PyT { #[new] fn new(obj: &PyRawObject, num: i32) { obj.init(PyT { num: num }) } fn get(&self) -> PyResult<i32> { Ok(self.num) } } #[pymodule] fn pyt(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::<PyT>()?; Ok(()) }
これらをビルドすると,pyt.dll
が作成されます. pyt.pyd
に名前を変更すれば,Pythonからimport文で呼び出せるようになります.
import pyt r = pyt.PyT(12) print(r.get())
これを実行すると,
12
となります.PythonでRustで作ったクラスを作成し,メンバを呼び出せることが分かりました.
Pythonのlistを扱う
一般的に,Python→Rustは,FromPyObjectのextract()を使います.Genericな関数なので,ほとんどの型に対応しています.
Rust→Pythonは,RustでPython用の変数を作成します.Pythonの代表的な型が作成できます.
pyo3/src/types at master · PyO3/pyo3 · GitHub
具体的に,Python List から Rust Vecに変換するには,v.extract::<Vec
lib.rs
use pyo3::prelude::*; use pyo3::types::PyList; #[pyclass(module = "pyt")] struct PyT { data: Vec<i32>, } #[pymethods] impl PyT { #[new] fn new(obj: &PyRawObject, d: &PyList) { let v = d.extract::<Vec<i32>>().unwrap(); obj.init(PyT { data: v }) } fn get<'p>(&self, py: Python<'p>) -> &'p PyList { let plist = PyList::new(py, &self.data); &plist } } #[pymodule] fn pyt(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::<PyT>()?; Ok(()) }
これをPythonから呼び出します.
import pyt r = pyt.PyT([1, 2, 3, 4, 5, 6, 7]) print(r.get())
[1, 2, 3, 4, 5, 6, 7]
となります.なお,int でない要素を含む list を使うと,Rustでエラーとなります.エラーをPythonに伝えるには後述します.
import pyt r = pyt.PyT([1, 2, 3, None, 5, 6, 7]) print(r.get())
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: PyErr { type: Py(0x7ffb6dfec670, PhantomData) }', src\lib.rs:13:17
tuple を含む list を扱う
型をVec<(String, i32)>のように変更すれば問題なく動作します.RustのGenerics凄い.
lib.rs
use pyo3::prelude::*; use pyo3::types::PyList; #[pyclass(module = "pyt")] struct PyT { data: Vec<(String, i32)>, } #[pymethods] impl PyT { #[new] fn new(obj: &PyRawObject, d: &PyList) { let v = d.extract::<Vec<(String, i32)>>().unwrap(); obj.init(PyT { data: v }) } fn get<'p>(&self, py: Python<'p>) -> &'p PyList { let plist = PyList::new(py, &self.data); &plist } } #[pymodule] fn pyt(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::<PyT>()?; Ok(()) }
Pythonから呼び出してみます.
r = pyt.PyT([ ("apple", 1), ("banana", 2), ("orange", 3) ]) print(r.get())
[("apple", 1), ("banana", 2), ("orange", 3)]
いろいろなデータを渡せます.
exceptionを使う
Rustでエラーが発生したときに,Pythonにその結果を伝える必要があります.
Pythonの標準的なexceptionは,pyo3::exceptions::ValueError::py_err("parse error.")などを使います*2.これをPyResultを通じて戻り値とします.
また,新たにset()を作りました.メンバ変数を変える関数なので,&mut self で書き換え可能にします.
lib.rs
use pyo3::exceptions; use pyo3::prelude::*; use pyo3::types::PyList; #[pyclass(module = "pyt")] struct PyT { data: Vec<(String, i32)>, } #[pymethods] impl PyT { #[new] fn new(obj: &PyRawObject) { obj.init(PyT { data: Vec::<(String, i32)>::new(), }) } fn get<'p>(&self, py: Python<'p>) -> &'p PyList { let plist = PyList::new(py, &self.data); &plist } fn set(&mut self, d: &PyList) -> PyResult<()> { match d.extract::<Vec<(String, i32)>>() { Ok(u) => { self.data = u; Ok(()) } Err(_) => Err(exceptions::ValueError::py_err("parse error.")), } } } #[pymodule] fn pyt(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_class::<PyT>()?; Ok(()) }
これを,Pythonからいろいろなデータを設定したり取得したりしてみます.
import pyt r = pyt.PyT() print(r.get()) r.set([ ("apple", 1), ("banana", 2), ("orange", 3) ]) print(r.get()) try: r.set([ (None, 1), # invalid data. ]) print(r.get()) except ValueError as e: print(e)
[] [('apple', 1), ('banana', 2), ('orange', 3)] parse error.
エラーが処理できることが分かりました.