LoginSignup
21
19

More than 3 years have passed since last update.

[Rust] PyO3 で Python パッケージを作成

Last updated at Posted at 2019-07-22

はじめに

Rust, 速いし書きやすいし最高ですよね? でも普段データをプロットしたり解析したりするには Python 使ってますよね? Python から Rust の関数を呼び出せたらもっと最高ですよね?

PyO3 は Rust で Python パッケージを作成, または Python を Rust から呼び出す機能を持つクレートです. 使い方を知るには基本的には ユーザーガイド (英語) を読めば良いと思いますが, 現在の状況に基づく日本語の文献が乏しいので, 2019年7月時点の最新版 v0.7.0 に基づいて基礎的な部分を説明します.

この記事では Rust で Python パッケージを作成する方法を扱います.

準備

Rust 1.34.0-nightly 以上, および Python 3.5 以上が実行可能な環境が既に整っていると仮定します. 2019年7月現在 nightly 必須 です (この issue). なお本記事の内容は 2019年7月22日に Rust 1.38.0-nightly (2019-07-19) + Python 3.5.3 (システム)/Python 3.7.2 (自前ビルド) on Debian 9.9 および Python 3.7.4 (ストア版) on Win10 1903 で検証しました.

作成したいパッケージ名を rust2python とします. 新しく lib クレートを作成し, Cargo.toml に次の内容を追記します.

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

[dependencies]
 pyo3 = { version = "0.7.0", features = [ "extension-module", ] }

型の対応関係

Rust で Python パッケージを作成する以上, Rust の型が Python の型に変換されます. これは PyO3 が自動的に遂行してくれますが, ある程度対応関係を把握しておかないとコードを書けません. そこで最初に型の対応関係についてまとめておきます.

Rust Python
i32, usize int
f32, f64 float
bool bool
Vec<T> list
String str
HashMap dict

つまりは普通に使う型は極めて普通に対応しているため, たいていうまくいきます. ちなみに関数の引数に入れようとして型エラーが発生した場合も普通に Python の方でエラーが発生するだけです.

なお struct/class に関することは ガイド を, numpy を扱う方法については rust-numpy を見てください.

パッケージ作成

それでは src/lib.rs の中身を作成しましょう. まずは use pyo3::prelude::*; により必要なものをインポートしておきます.

次に, Python から呼び出したい関数を #[pyfunction] アトリビュート付きで実装します. 戻り値は必ず PyResult<T> である必要があります. 戻り値のない関数を定義する場合は PyResult<()> とすればよいです. 例えば hello world なら


#[pyfunction]
fn hello() -> PyResult<()> {
    println!("Hello, world!");
    Ok(())
}

引数や戻り値がある場合もごく素直に書けばよいです. 例えば円周率の 0 倍, 1 倍, ..., n-1 倍のリストをつくる関数なら

use std::f64::consts::PI;

#[pyfunction]
fn pi_times( n: usize ) -> PyResult<Vec<f64>> {
    Ok(
        (0..n).map(|i| i as f64 * PI).collect()
    )
}

必要な関数を実装し終えたら, それを Python モジュールにまとめます. これは [#pymodule] アトリビュート付きの関数として実現されます.

use pyo3::wrap_pyfunction;

#[pymodule]
fn rust2python(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!( hello ))?;
    m.add_wrapped(wrap_pyfunction!( pi_times ))?;

    Ok(())
}

これを cargo +nightly build --release みたいな感じでビルドすれば完成です. ./target/release/librust2python.so が欲しかったパッケージです.

Python から呼び出す

作成したパッケージを Python から呼び出すためには, ./target/release/librust2python.so をカレントディレクトリにコピーします. 同時に, Linux なら librust2python.sorust2python.so へ, Windows なら librust2python.dllrust2python.pyd へとリネームします. (コピーするより symlink を貼る方が楽なことが多い気もします.)

$ cp ./target.release/librust2python.so ./rust2python.so

これでめでたく Rust 製パッケージを呼び出せるようになりました.

$ python3
Python 3.7.2 (default, Jan 24 2019, 15:36:28)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import rust2python
>>> 
>>> rust2python.hello()
Hello, world!
>>> rust2python.pi_times(4)
[0.0, 3.141592653589793, 6.283185307179586, 9.42477796076938]

関連項目

PyO3 に関するより詳細な情報が必要な場合, ユーザーガイド を読んでください. なお こちらの記事 は依存クレートなしで同じことを試みています.

追記 (2019-07-24) モジュールの作成に関する 続編 書きました.

21
19
1

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
21
19