1
2

More than 3 years have passed since last update.

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

Posted at

概要

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

これは、PythonからRustを呼んで高速化! PyO3 チュートリアル:クラスをラップする その➀

の続きになります。

目標

目標は、
Rustのクラス(struct + method)を定義し、それをPythonから呼ぶ
でしたが、
前回でRustで書かれたクラスをPythonから呼ぶ方法を解説しました。

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

今回は、クラスメソッドをいくつか追加して、RustからPythonへの型にPyO3経由で変換するところを解説していきます。

PyO3のgitレポジトリ(ここ)を参照し、

RustのオブジェクトをPython側にどうPyO3で引き渡すかを解説します。

  • Vec -> List
  • Hashmap -> Dict
  • Vec -> Tuple

などです。

手順

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

おさらいとして、前回はクラスのコンストラクタ、プロパティのnumgetter, setter をPyO3経由でPythonから呼べるようにしました。
以下が、前回完成させたコードです。


//lib.rs
use pyo3::prelude::*;
use pyo3::{wrap_pyfunction};


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

#[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(())
    }
}


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

このMyClassに、今回は関数を6つ追加します。
早速ですが、以下がコードです。


//lib.rs

use pyo3::types::PyType;
use pyo3::types::PyInt;
use pyo3::types::PyList;
use pyo3::types::PyTuple;
use pyo3::types::PyDateTime;
use std::collections::HashMap;

#[pymethods]
impl MyClass{

    fn test1(&self) -> PyResult<bool>{
        if self.num > 3{
            Ok(true)
        }else{
            Ok(false)
        }
    }

    fn test2(&self) -> PyResult<String>{
        if self.debug == true{
            let result: &str = "your debug is True";
            Ok(result.to_string())
        }else{
            let result: &str = "your debug is False";
            Ok(result.to_string())
        }
    }

    fn test3<'py>(&self, py: Python<'py>) -> PyResult<&'py PyList>{
        let mut vec = vec![1,2,3];
        let result = PyList::new(py, &vec);
        Ok(result)
    }

    fn test4(&self, py: Python) -> PyResult<PyObject>{
        let mut map = HashMap::new();
        map.insert("key1", 1);
        map.insert("key2", 2);
        map.insert("key3", 3);
        assert_eq!(map["key1"], 1);
        assert_eq!(map["key2"], 2);
        assert_eq!(map["key3"], 3);
        Ok(map.to_object(py))

    }

    fn test5(&self) -> PyResult<f64>{
        let result:f64 = 1.23;
        Ok(result)
    }

    fn test6<'py>(&self, py: Python<'py>, dt: &PyDateTime) -> PyResult<&'py PyTuple>{
        let mut vec = vec![3,4,5];
        let result = PyTuple::new(py, &vec);
        Ok(result)

    }
}

fn test1

Rustのboolを、PythonのboolへとPyO3経由で受け渡ししています。

fn test2

RustのStringを、PythonのstrへとPyO3経由で受け渡ししています。

fn test3

RustのVecを、PythonのListへとPyO3経由で受け渡ししています。
この書き方がきちんと理解できてません。。

fn test4

RustのHashmapを、PythonのDictへとPyO3経由で受け渡ししています。

fn test5

Rustのf64を、PythonのfloatへとPyO3経由で受け渡ししています。

fn test6

RustのVecを、PythonのtupleへとPyO3経由で受け渡ししています。この際、関数の引数として、PythonのDatetimeを受け取っています。
この書き方がきちんと理解できてません。。

testを実行する

test3, test6に関しては、正直書き方がうまく理解できていませんが、テストを実行します。
前回と同じようにCargo.tomlsetup.pyを用意することで、

python setup.py install

でビルドすることができます。

その後、test.pyを以下のように準備し、テストを実行します。

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)
    result = test.test1()
    print(result)
    print(type(result))

    result = test.test2()
    print(result)
    print(type(result))

    result = test.test3()
    print(result)
    print(type(result))

    result = test.test4()
    print(result)
    print(type(result))

    result = test.test5()
    print(result)
    print(type(result))

    import datetime 
    now = datetime.datetime.now()
    result = test.test6(now)
    print(result)
    print(type(result))

まとめ

今回は、クラスメソッドをいくつか追加して、RustからPythonへの型にPyO3経由で変換するところを解説しました。
Cythonよりは比較的わかりやすい感覚があるものの、PyO3自体バージョンがどんどん更新されているため、一番いいのはバージョンをきちんと意識して(Fixして)開発するか、
もしくはGitをきちんと追い、APIの呼び方の変更にきちんと気を配るべきでしょう。

ただ、Rust面白いので、これからも勉強していこうと思います。

今回はこの辺で。

おわり。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2