概要

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として受け取ります。
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経由で呼ぶことができました。

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

今回はこの辺で。

おわり。

广告
将在 10 秒后关闭
bannerAds