PyO3では、Pythonの例外クラス(Rustの構造体)はcreate_exception!マクロで作成できる。
例外の基底クラス(継承元クラス)としてPyException等の構造体(Pythonのクラス)が用意されている。
use pyo3::{create_exception, exceptions::PyException};
create_exception!(モジュール名, 例外名, 継承元クラス名);
create_exception!(モジュール名, 例外名, 継承元クラス名, 説明文);
「モジュール名.例外名」の例外クラス(Rustの構造体)が定義される。
説明文を付けておくと、「例外クラス.__doc__」でその説明文が取得できる。(説明文を指定しなかった場合はNoneが返る)
「help(例外クラス)」でも表示される。
例外のインスタンスは「例外クラス::new_err(メッセージ)」で生成する。
これはPyErr型であり、戻り型がPyResultである関数からErrで返すと、Python側では例外が送出される。
例外を送出するだけなら例外クラス(構造体)を定義しておくだけでいいが、それをPython側で使いたい場合(try〜exceptに記述する場合等)は、例外クラス(構造体)をエクスポート (pymodule_export)する必要がある。
use pyo3::{create_exception, exceptions::PyException};
create_exception!(example_pyo3, MyError, PyException);
create_exception!(example_pyo3, MyError2, PyException);
create_exception!(example_pyo3, MyError3, MyError);
use pyo3::prelude::*;
mod error;
#[pymodule]
mod example_pyo3 {
use pyo3::prelude::*;
#[pymodule_export]
use crate::error::{MyError, MyError2, MyError3};
#[pyfunction]
fn throw_exception(n: i32) -> PyResult<()> {
match n {
2 => Err(MyError2::new_err("error message2")),
3 => Err(MyError3::new_err("error message3")),
_ => Err(MyError::new_err("error message1")),
}
}
}
pymodule_exportで例外クラスをエクスポートする(Python側から使えるようにする)。
(pymodule_exportでは*を使えない。すなわち「use create::error::*;」と書くことは出来ない)
import example_pyo3
def main():
try:
example_pyo3.throw_exception(3)
except example_pyo3.MyError as e:
print(f"1: {e}")
except example_pyo3.MyError2 as e:
print(f"2: {e}")
if __name__ == "__main__":
main()
↓
> uv run main.py 1: error message3
この例では、throw_exception(3)はMyError3が発生するが、MyError3はMyErrorを継承しているので、MyErrorとして捕捉される。