PyO3 + PoetryでPythonからRust実装を使う
はじめに
Rustで作ったライブラリーを利用するときにはPyO3が便利である。これを用いると簡単にRust実装をPythonから利用することができる。このようなRust実装のライブラリーはWheel形式で各アーキテクチャー(x86_64向けなど)ごとにバイナリ形式で配布すると、利用者がRust環境を構築しなくてもよくなって便利である。この記事ではPyO3の簡単な使い方を解説し、PyO3が配布しているMaturinをPoetryから利用してPythonからRust実装をテストしたり、Wheel作成する方法を述べる。
この記事で利用するコードは下記のGitHubリポジトリーで公開されている。
つくったもの
下記のようにPythonのデータ構造を定義し、pytomlrs.to_toml
でTOMLへと変換できる。
import pytomlrs
python_value = {
'a': {
'b': 1,
'c': True
},
'd': {
'e': [1.2, 'foobar']
}
}
print(pytomlrs.to_toml(python_value))
[a]
b = 1
c = true
[d]
e = [1.2, "foobar"]
Rustによる実装
Cargo.toml
まずCargo.toml
に今回用いるpyo3
の依存を追加する。
[package]
name = "pytomlrs"
version = "0.1.0"
edition = "2021"
[dependencies]
pyo3 = { version = "0.19.*", features = [ "extension-module" ] }
serde = "1.0.*"
toml = { version = "0.7.6", features = [ "parse" ] }
メソッド作成
次にPythonから呼び出されるメソッド実装を作っていく[1]。
#[pyfunction]
pub fn to_toml(py: Python, obj: PyObject) -> PyResult<PyObject> {
/* 実装 */
}
#[pyfunction]
pub fn from_toml(py: Python, obj: PyObject) -> PyResult<PyObject> {
/* 実装 */
}
#[pymodule]
fn pytomlrs<'py>(_py: Python<'py>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(to_toml, m)?)?;
m.add_function(wrap_pyfunction!(from_toml, m)?)?;
Ok(())
}
これでRust側の準備は終了となる。
Pythonからの呼び出し
pyproject.toml
の設定
今回の記事ではPoetryの利用を前提としているため、PoetryからRust製ライブラリーを利用するための設定を下記のように行っておく。
[tool.poetry]
name = "pytomlrs"
version = "0.1.0"
description = ""
authors = [ "YOSHIMURA Yuu <yyu@mental.poker>" ]
[tool.poetry.dependencies]
python = ">=3.9,<4.0"
[tool.poetry.dev-dependencies]
wheel = "*"
pytest = "^7.4"
maturin = "==1.2.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
基本的にはmaturin
の依存を追加するだけである。
__init__.py
を設定
パッケージ名のディレクトリーにこれがどうして必要になるのかよく理解できてはいないが、下記のようなディレクトリー構成でpytomlrs/__init__.py
のようなパッケージ名と同じディレクトリーの中に__init__.py
を設置する必要がある。
.
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── poetry.lock
├── pyproject.toml
├── pytomlrs
│ └── __init__.py
├── src
│ ├── lib.rs
│ └── serde_pyobject.rs
└── tests
├── __init__.py
└── test_pytomlrs.py
pytomlrs/__init__.py
は次のような内容になる。
from .pytomlrs import *
インストール
ここまででRust製ライブラリーをpipにインストールする準備ができたので、次のコマンドでPoetry配下のpipにインストールする。
$ poetry run maturin develop
テスト
設定まわりができたところで、Pythonから利用するテストを書いて実行する。下記のようなテストを用意して、実際にTOML変換が上手くいっているかチェックする。
import pytomlrs
python_value = {
'a': {
'b': 1,
'c': True
},
'd': {
'e': [1.2, 'foobar']
}
}
expected_toml = """
[a]
b = 1
c = true
[d]
e = [1.2, "foobar"]
""".strip()
def test_to_toml():
actual = pytomlrs.to_toml(python_value)
assert actual.strip() == expected_toml
def test_from_toml():
actual = pytomlrs.from_toml(expected_toml)
assert actual == python_value
これをpytest
で次のように実行する。
$ poetry run pytest ./tests
============================================= test session starts =============================================
platform darwin -- Python 3.10.7, pytest-7.4.0, pluggy-1.2.0
rootdir: /Users/yyu/Desktop/pytomlrs
collected 2 items
tests/test_pytomlrs.py .. [100%]
============================================== 2 passed in 0.23s ==============================================
Wheel作成
次のコマンドでWheelを作成できる。
$ poetry run maturin build --release
GitHub Actionsと連携する場合はPyO3/maturin-action
が便利である。
まとめ
このようにRustの実装をPythonから呼び出して、それをテストしWheelを作成する方法を紹介した。
-
今回の記事はTOML変換の実装を解説するというよりは、Pythonからの使い方を解説するので
to_toml
/from_toml
の実装詳細は省略する。これらの実装が気になる場合はGitHubリポジトリーを参考にしてほしい。 ↩︎
Discussion