LoginSignup
9
10

More than 3 years have passed since last update.

Pyo3でPythonとRustを連携させる

Posted at

普段はPython使いなのですが、可視化部分等はPythonでそれ以外はRustで高速できないかと考え調べてみました.

今回はPyo3を使ってRustとPythonをつないでみました.

Pythonのモジュールとして使われるように,Rustのコードを書いてます

ドキュメントはあるものの、情報量もまだまだ足りないので,わからない所含めまとていきます.

ちなみにRustとPyo3の記事だと

を参考にしました.

注意 rustのバージョン
現在,pyo3を使うにはnighty 版が必須です.
これはissueにも記載がありますが,いくつか必要な機能がstableでは提供されていないためです.

Cargo.toml

公式のサンプルを例にPyo3を使うのに必要な設定について説明します.

[package]
name = "string-sum"
version = "0.1.0"
edition = "2018"

[lib]
name = "string_sum"
crate-type = ["cdylib"]

[dependencies.pyo3]
version = "0.7.0"
features = ["extension-module"]

使うセクションは

  • [package]
  • [lib]
  • [dependencices] の三種類です.

[package]はcrate用のセクションで,cargoを使ったことがあれば実際にわかると思います.
変数は以下になります.

  • name: rustで参照するpackage名
  • version: アプリケーションのバージョン

[lib]はライブラリの出力のセクションです.

  • name:build時に作れるファイルの名前です.
  • crate-type: 出力のタイプを指定するものです.

Pythonで使うなら、基本Python用の動的ライブラリになるので、["cdylib"]を指定すればよいです.
詳細はここを御覧ください.

[dependencies] は依存ライブラリのついて記載する所です.

  • dependencies.pyo3でpyo3に関する条件を記載する箇所になります.
  • versionは指定するバージョン
  • featuresは条件つきコンパイルを指定する所で今回は外部モジュールとしてコンパイルすることを指定しています.

なので、実際にpyo3を使って開発したい場合はCargo.tomlからnameだけ変更すればよいのかと思います.

実際のソース

実際にPythonから呼び出す方法を公式のサンプルから説明します.

まずPythonのモジュールとしてみせたいものを
#[pymodule] をつけた関数として定義します.
引数の型は{ythonのプロセスを表すPython型と, Pythonのモジュールを表すPyModule型になります.

Rustのattributeは基本決まっていますが,
proc_macro_attributeを使って新たに作ることができます.(nighty版だけの機能のようですが)

attributeはPythonでいうデコレータになります.おかげで特に何も考えずに使えます.

実際にpythonから呼び出したいオブジェクトを定義してきます.

関数を呼び出す場合

Pythonから呼び出す関数の場合は #[pyfunction] を設定すればよいです.

公式そのままですが、以下のようにすれば動きます.

use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

#[pyfunction]
/// Formats the sum of two numbers as string
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

/// This module is a python module implemented in Rust.
#[pymodule]
fn string_sum(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!(sum_as_string))?;

    Ok(())
}

注意点ですが, 返り値はPyResultにする必要があります.
また,OK()は返り値をPyResultにしてくれます.
返り値がない場合は,Ok(())とし、型としてはPyResult<()>とすればよいです.

配列の返却方法

さっきの例だとPython側ではStringが返ります.
今度はPython側でlistを返してみます.

#[pyfunction]
fn make_list<'p>(py: Python<'p>) -> PyResult<&'p PyList> {
    let v1 = vec![vec![vec![99; 3]; 2]; 10];
    let list = PyList::new(py, v1);
    Ok(list)
}

でできます.

注意

  • listの場合はPyList型にする必要があります.
  • #[pyfunction] が着いている場合,引数:py は普通は指定する必要がありません.
    ただlistの場合はnewで引数として渡す必要があるため,指定しています.

  • ライフタイム'pはpyfunctionとして使用する限りは不要のようです.
    ただ,#[pymethods]で実行する場合、ライフタイムを指定しないとエラーになります.
    ソースをおえていないので実態はわからないのですが,pyfunctionはpymoduleにライフタイムが紐づく?ので,ライフタイムを指定せずともエラーにならないのかなと思いました.

クラスの場合

Rust側ではclassを返却するには二つの処理が必要です.

  1. #[pyclass]をつけた構造体の定義
  2. m.add_class::<クラス名> をpymodule内に定義する.

これができれば,動きます.

  • newの設定方法

#[new]をつけて以下のようにnewメソッドで実行すると,new相当になります.
実際に例を書くと以下みたいな形になります.

#[pyclass]
pub struct Hoge {
    pub x: usize,
}

#[pymethods]
impl Hoge {
    #[new]
    fn new(obj: &PyRawObject, x: usize){
            obj.init({
            Hoge {
                x: x,
            }
        });
    }

他にもgetter,setterが定義できたりします.

Numpy

ndarrayとやり取りできるはずですが、少し頑張ってもエラーになったので、今回はスキップします.
githubはあるものの、そこのexampleも
#![deny(rust_2018_idioms)]になっていて,これを外すと動きませんでした.

テスト

どうやらPyo3がある状況だとMacでCargo testをするとエラーになるようです.
以下のみの実行がエラーになります.(他のPyo3が使わないプロジェクトでエラーにならないことは確認しました)

#[cfg(test)]
    #[test]
    fn it_works() {
        assert!(false);
    }
    Finished dev [unoptimized + debuginfo] target(s) in 3.19s
     Running target/debug/deps/python_rust_example-3e2a90b01ebe111d
dyld: Symbol not found: _PyExc_OverflowError
  Referenced from: python_rust_example/target/debug/deps/python_rust_example-3e2a90b01ebe111d
  Expected in: flat namespace
 in python_rust_example/target/debug/deps/python_rust_example-3e2a90b01ebe111d
error: process didn't exit successfully: `python_rust_example/target/debug/deps/python_rust_example-3e2a90b01ebe111d` (signal: 6, SIGABRT: process abort signal)

この辺は解決するべきですが,その前に夏休みが終わってしまったので,今回はこの辺でお別れです(涙)

まとめ

今回は,最低限Pyo3の使い方を解説しました.
Pyo3はまだまだ枯れきっていないのか,調べてみるとかなりトラブルが発生しました.
そうしたトラブル自体が勉強になることもあるので、自分でも使い込みつつどこかでコミットできたらなと思います.

9
10
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
9
10