PythonからRustを呼んで高速化！ PyO3 チュートリアル：クラスをラップする その➀

## 概要

1.　Pyhtonでアルゴリズムまで書いてあるのは速度面では好ましくないな〜
2.　よし、C, C++あたりで書いてあるものを探して、それをPythonから呼んで高速化しよう。
3.　なかなかいいライブラリ見つからんな、
4.　おっ、Rustていう言語で書かれてるのならあったぞ
5.　RustてPythonから呼べんのか？？

これは、[PythonからRustを呼んで高速化！ PyO3 チュートリアル：簡単な関数をラップする　その➁](https://qiita.com/kenmaro/items/88326e94da9509d3b815)

の続きになります。


## 目標
`Rustのクラス（struct + method）を定義し、それをPythonから呼ぶ`
ことを目標にします。

今回は、クラスのパースの仕方と、getter, setterのPyO3経由での呼び方まで解説します。


## 手順
前回`cargo new --lib example`で作ったプロジェクトを使用します。
新しく作ってももちろん問題ありません。


### クラスの宣言
`lib.rs` に以下のように書きます。

```rust

//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,
}

```

ここで、

```rust

#[pyclass(module = "my_class")]
```
により、PyO3を経由してPythonから呼べるようにしています。`(module = "my_class")`はおまじないのように書きましたが、あまり意味がわかっていません。すみません。

ここで、`MyClass`は`Struct`として定義され、プロパティとして
`num: i32` と　`debug: bool`を持っています。

まず、このクラスをPythonのオブジェクトとして呼べるようにするため、コンストラクタを書きます。

```rust

#[pymethods]
impl MyClass{
    #[new]
    fn new(num:i32, debug:bool) -> Self{
        MyClass{
            num: num,
            debug: debug
        }
    }
```

ここで、`impl`の上に

```rust
#[pymethods]
```
と宣言（デコレート？）されていること、
コンストラクタを示す`fn new(num:i32, debug:bool)`の上にも

```rust
#[new]
```
と宣言されていること、に注意します。

### クラスのモジュールへの追加

これを、以前の関数のように以下のようにモジュールに追加します。

```rust

//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(())
}
```

ここで、

```rust
m.add_class::<MyClass>()?;
```
の行でMyClassをモジュールに追加しています。


### getter, setter の追加
今回は、クラスの宣言に加えて、プロパティのgetter, setterに関してもPythonから呼べるように書きます。

```rust
//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オブジェクトを引き渡す時は、

```rust

Ok(self.num)

Ok(())
```
を使って引き渡すことは前回と同じです。


### setup.py を使ってコンパイル
`setup.py`, `Cargo.toml` に関しては前回と同じものですが、念のため書いておくと、

```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"]
```


```setup.py
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)

```

となります。

```bash
python setup.py install
```

を行うことで、コンパイルができます。

### テスト実行

以下のようなテストプログラムを実行すると、

```test.py
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)
```

```bash
$ python test.py

test for class
2
4
```
となり、確かにクラスをコンストラクトでき、プロパティのget,setが実行できたことがわかりました。



## まとめ
目標は、
`Rustのクラス（struct + method）を定義し、それをPythonから呼ぶ`
でしたが、
今回でRustで書かれたクラスをPythonから呼ぶ方法を解説しました。

クラスのコンストラクタおよび、プロパティのgetter,setterもPythonからPyO3経由で呼ぶことができました。

次回は、クラスのメソッドをいくつか追加して、色々な型変換を解説できたらと思います。


今回はこの辺で。

おわり。