概要
1. Pyhtonでアルゴリズムまで書いてあるのは速度面では好ましくないな〜
2. よし、C, C++あたりで書いてあるものを探して、それをPythonから呼んで高速化しよう。
3. なかなかいいライブラリ見つからんな、
4. おっ、Rustていう言語で書かれてるのならあったぞ
5. RustてPythonから呼べんのか??
これは、PythonからRustを呼んで高速化! PyO3 チュートリアル:簡単な関数をラップする その➁
の続きになります。
目標
Rustのクラス(struct + method)を定義し、それをPythonから呼ぶ
ことを目標にします。
今回は、クラスのパースの仕方と、getter, setterのPyO3経由での呼び方まで解説します。
手順
前回cargo new --lib example
で作ったプロジェクトを使用します。
新しく作ってももちろん問題ありません。
クラスの宣言
lib.rs
に以下のように書きます。
//lib.rs
use pyo3::prelude::*;
// ======================RUST CLASS TO PYTHON ======================================
/// Class for demonstration
// this class, MyClass can be called from python
#[pyclass(module = "my_class")]
struct MyClass {
num: i32,
debug: bool,
}
ここで、
#[pyclass(module = "my_class")]
により、PyO3を経由してPythonから呼べるようにしています。(module = "my_class")
はおまじないのように書きましたが、あまり意味がわかっていません。すみません。
ここで、MyClass
はStruct
として定義され、プロパティとして
num: i32
と debug: bool
を持っています。
まず、このクラスをPythonのオブジェクトとして呼べるようにするため、コンストラクタを書きます。
#[pymethods]
impl MyClass{
#[new]
fn new(num:i32, debug:bool) -> Self{
MyClass{
num: num,
debug: debug
}
}
ここで、impl
の上に
#[pymethods]
と宣言(デコレート?)されていること、
コンストラクタを示すfn new(num:i32, debug:bool)
の上にも
#[new]
と宣言されていること、に注意します。
クラスのモジュールへの追加
これを、以前の関数のように以下のようにモジュールに追加します。
//lib.rs
use pyo3::{wrap_pyfunction};
// =================CREATING MODULE FOR PYTHON=========================
/// This module is a python module implemented in Rust.
#[pymodule]
fn test_library(py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(get_prime_numbers))?;
m.add_class::<MyClass>()?;
Ok(())
}
ここで、
m.add_class::<MyClass>()?;
の行でMyClassをモジュールに追加しています。
getter, setter の追加
今回は、クラスの宣言に加えて、プロパティのgetter, setterに関してもPythonから呼べるように書きます。
//lib.rs
#[pymethods]
impl MyClass{
#[new]
fn new(num:i32, debug:bool) -> Self{
MyClass{
num: num,
debug: debug
}
}
#[getter]
fn get_num(&self) -> PyResult<i32>{
Ok(self.num)
}
#[setter]
fn set_num(&mut self, num: i32) -> PyResult<()>{
self.num = num;
Ok(())
}
}
このように、#[getter]
, #[setter]
のデコレータを使うことで、PyO3経由でPythonからgetter, setterも呼べます。
今回はプロパティであるnum
についてだけ書きました。
getterの返り値に関しては、num
の型がi32
であることから、Python側はPyResult<i32>
として受け取ります。
setterの返り値はないので、PyResult<()>
と書けます。
どちらもPythonオブジェクトを引き渡す時は、
Ok(self.num)
Ok(())
を使って引き渡すことは前回と同じです。
setup.py を使ってコンパイル
setup.py
, Cargo.toml
に関しては前回と同じものですが、念のため書いておくと、
[package]
name = "test"
version = "0.1.0"
edition = "2018"
[lib]
name = "test_library"
crate-type = ["cdylib"]
[dependencies.pyo3]
version = "0.9.1"
features = ["extension-module"]
from setuptools import setup
from setuptools_rust import Binding, RustExtension
setup(name='ope_rust',
version='0.1',
rust_extensions=[
RustExtension('ope_rust', 'Cargo.toml',
binding=Binding.PyO3)],
zip_safe=False)
となります。
python setup.py install
を行うことで、コンパイルができます。
テスト実行
以下のようなテストプログラムを実行すると、
import test_library
if __name__ == "__main__":
# Testing class
print("\ntest for class")
num = 2
debug = True
test = test_library.MyClass(num=num, debug=debug)
print(test.num) # getter test
test.num = 4 # setter test
print(test.num)
$ python test.py
test for class
2
4
となり、確かにクラスをコンストラクトでき、プロパティのget,setが実行できたことがわかりました。
まとめ
目標は、
Rustのクラス(struct + method)を定義し、それをPythonから呼ぶ
でしたが、
今回でRustで書かれたクラスをPythonから呼ぶ方法を解説しました。
クラスのコンストラクタおよび、プロパティのgetter,setterもPythonからPyO3経由で呼ぶことができました。
次回は、クラスのメソッドをいくつか追加して、色々な型変換を解説できたらと思います。
今回はこの辺で。
おわり。