PyO3 version 0.27を使って、Pythonから呼べるRustライブラリーを作る例。
PyO3を使ったRustライブラリーのプロジェクト作成にはmaturinを使うのが便利。
maturinをインストールするには、先にPythonをインストールおよびuvをインストールしておく。
maturin 1.10.2はPython 3.8以降に対応しているようだ。
uv tool install maturin
> uv run maturin --version maturin 1.10.2
Windows11でPyO3を使うライブラリープロジェクトを作る例。
以下のコマンドで、example-pyo3という名前のRustプロジェクトを作成できる。
(生成されたCargo.tomlを見ると、pyo3のバージョンは0.27.0だった)
uv run maturin new example-pyo3
途中でどのライブラリーを使うか聞かれるので、pyo3を選ぶ。
以下のようなディレクトリーとファイルが生成される。
初期状態のlib.rsには以下のようなコードが書かれている。
use pyo3::prelude::*;
/// A Python module implemented in Rust.
#[pymodule]
mod example_pyo3 {
use pyo3::prelude::*;
/// Formats the sum of two numbers as string.
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
Ok((a + b).to_string())
}
}
※古いバージョンのpyo3だと、pymoduleはfnで定義してpyfunctionをpyに登録する必要があったが、pyo3 0.27ではその手間が無くなっている。
初期生成されている雛形の関数は、以下のようにして実行することが出来る。
> cd example-pyo3 > uv run python >>> import example_pyo3 >>> example_pyo3.sum_as_string(10, 5) '15'
実行すると、初回は、Rustライブラリーがビルドされる。
また、venvのPython仮想環境が無い場合は自動的に作られる。
デフォルトでは、src/lib.rsを更新してuv runしても反映されない(ビルドされない)。
この場合、--reinstallオプションを付けて実行すればいい。
uv run --reinstall python
ソースファイルを修正してuv runしたときに自動的にビルドされるようにするには、pyproject.tomlに以下の設定を追加する。
〜
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { file = "src/**/*.rs" }, { file = "Cargo.toml" }, { file = "Cargo.lock" }]
こうしておけば、(--reinstallを付けなくても)ファイルの更新を検知して自動的にビルドしてくれる。
上記のRustプロジェクトの関数を呼び出すPythonプロジェクトを作成する例。
Rust用のexample-pyo3プロジェクトの隣に、Python用のcall-pyo3プロジェクトを作成する。
uv init --python 3.10 call-pyo3
以下のようなディレクトリーとファイルが生成される。
初期状態のmain.pyは以下のようになっている。
def main():
print("Hello from call-pyo3!")
if __name__ == "__main__":
main()
以下のようにして実行できる。
cd call-pyo3 uv run main.py
venvのPython仮想環境が無い場合は自動的に作られる。
Pythonのcall-pyo3プロジェクトから、Rustのexample-pyo3プロジェクトの関数を呼んでみる。
call-pyo3からexample-pyo3の関数を呼ぶ為には、example-poy3のライブラリーをインストールするか、依存ライブラリーに追加する必要がある。
が、それはさておき、example-pyo3の関数を呼ぶ実装は以下の通り。
import example_pyo3
def main():
print("Hello from call-pyo3!")
print(example_pyo3.sum_as_string(10, 5))
if __name__ == "__main__":
main()
example-pyo3のライブラリーをcall-pyo3にインストールする。
cd call-pyo3 uv pip install ../example-pyo3
※venvのPython仮想環境が作られていない場合はエラーになるので、その場合は「uv venv」でPython仮想環境を作成する。
これでmain.pyを実行すると、Rustで作った関数も呼ばれているのが確認できる。
> uv run main.py Hello from call-pyo3! 15
call-pyo3の依存ライブラリーにexample-pyo3を追加する。
cd call-pyo3 uv add ../example-pyo3
venvのPython仮想環境が無い場合は自動的に作られる。
上記のようにuv addを実行すると、pyproject.tomlに以下のような設定が追加される。
〜
dependencies = [
"example-pyo3",
]
[tool.uv.sources]
example-pyo3 = { path = "../example-pyo3" }
これでmain.pyを実行すると、Rustで作った関数も呼ばれているのが確認できる。
> uv run main.py Hello from call-pyo3! 15
なお、依存ライブラリーに追加する方法の場合、exmaple-pyo3側で[tool.uv]のcache-keysの設定をしておくと、example-pyo3のlib.rsを変更したら、uv run時に自動的に再ビルドされる。
(デフォルトでは、example-pyo3/pyproject.tomlファイルが更新されると、uv run時に再ビルドされるようだ)
Pythonの定数はRustのconstで定義できる。
use pyo3::prelude::*;
#[pymodule]
mod example_pyo3 {
use pyo3::prelude::*;
#[pymodule_export]
const MY_VERSION: &str = "0.1.0";
}
import example_pyo3
def main():
print(example_pyo3.MY_VERSION)
if __name__ == "__main__":
main()
モジュールの初期処理を行うには、#[pymodule_init]を付けた関数を用意する。
古いバージョンのPyO3のようにPyModuleを使って初期化したい場合にも利用できる。
例えばPythonの定数は以下のようにして定義することが出来る。
use pyo3::prelude::*;
#[pymodule]
mod example_pyo3 {
use pyo3::prelude::*;
/// モジュール初期化時に呼ばれる
#[pymodule_init]
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add("MY_VERSION", "0.1.0")?;
Ok(())
}
}
import example_pyo3
def main():
print(example_pyo3.MY_VERSION)
if __name__ == "__main__":
main()