RustからPythonのモジュールを使うときは、 PyO3
が便利です。
PyO3/pyo3: Rust bindings for the Python interpreter
ただ、公式ドキュメントでは、Pythonの標準モジュールをimportして実行する方法は記載されていたものの、自作のPythonモジュールについては記載が見当たりませんでした。
Calling Python from Rust - PyO3 user guide
そこで、調べたことをメモしておきます。
目次
環境
- macOS 10.14.6
- Rust 1.45.2
- Python 3.8
- PyO3 0.11.1
- 以前はRustのnightlyビルドが必要だったようですが、0.11よりstableなRustで扱えるようです。
自作のPythonモジュール
以下の内容を hello.py
として用意します。この中の say()
関数をRustで使いたいとします。
def say(message): return f'hello, {message}'
ディレクトリ構造
$ tree . ├── Cargo.lock ├── Cargo.toml ├── hello.py ├── src │ └── main.rs └── target
ダメな方法
標準モジュールのように
let gil = Python::acquire_gil(); let py = gil.python(); let hello = py.import("hello")?;
とimportした場合、コンパイルは通るものの、実行すると以下のエラーになります。
Error: PyErr { type: Py(0x10529f7a8, PhantomData) }
対応:sys.pathにエントリを追加する
以下のissueにある通り、自作モジュールがsys.pathに追加されていないため、PyO3を使って追加します。
How can I import Python code into Rust? · Issue #148 · PyO3/pyo3
今回は、カレントディレクトリにPythonファイルを置いたため、カレントディレクトリを sys.path
に追加します。
let syspath: &PyList = py.import("sys") .unwrap() .get("path") .unwrap() .try_into() .unwrap(); let path = env::current_dir()?; // path.display()をそのまま渡すとエラーになるので、文字列にして渡す // error[E0277]: the trait bound `std::path::Display<'_>: pyo3::conversion::ToPyObject` is not satisfied // syspath.insert(0, path.display()).unwrap(); syspath.insert(0, format!("{}", path.display())).unwrap();
これで import できるようになりました。
全体像はこんな感じです。
use pyo3::prelude::*; use pyo3::types::PyList; use std::env; fn main() -> PyResult<()> { let gil = Python::acquire_gil(); let py = gil.python(); let syspath: &PyList = py.import("sys") .unwrap() .get("path") .unwrap() .try_into() .unwrap(); let path = env::current_dir()?; // error[E0277]: the trait bound `std::path::Display<'_>: pyo3::conversion::ToPyObject` is not satisfied // syspath.insert(0, path.display()).unwrap(); syspath.insert(0, format!("{}", path.display())).unwrap(); let hello = py.import("hello")?; let response: String = hello.call1("say", ("Shinano Gold", ))?.extract()?; println!("{}", response); Ok(()) }
cargo run
すると、say()関数の結果が表示されました。
$ cargo run Compiling using_python_from_rust_by_pyo3 v0.1.0 Finished dev [unoptimized + debuginfo] target(s) in 2.83s Running `target/debug/using_python_from_rust_by_pyo3` hello, Shinano Gold
なお、READMEには
Python::with_gil(|py| { /**/ }
のような書き方がされていますが、これは次のバージョン (0.12) よりサポートされるようです。
no function or associated item named with_gil
found for struct pyo3::python::Python<'_>
· Issue #1079 · PyO3/pyo3
そのため、今のバージョンで書こうとすると、 with_gil
のところでコンパイルエラーになります。
その他
RustでPythonを扱う方法としては、PyO3の他に rust-cpython
もあります。
dgrunwald/rust-cpython: Rust <-> Python bindings
ただ、GitHubのstar数や、rust-numpyがバージョン0.3以降rust-pythonからPyO3に移ったことを考えて、今回はPyO3を使いました。
PyO3/rust-numpy: PyO3-based Rust binding of NumPy C-API
PyO3については、メンテナの方の記事が詳しいです。
PyO3: これまで/これから - Qiita