使用pyO3时如何在Python中捕获第三方Rust库的Panic?
如何在Python中捕获PyO3导出的Rust函数的Panic?
我用PyO3将部分Python代码重写为Rust,目前遇到问题:所依赖的第三方Rust库可能会触发panic。我希望能在Python环境中捕获该panic,回退到速度较慢但更稳定的Python实现方法。由于panic触发概率极低,这种回退方案仍比纯Python实现更高效。根据现有资料,答案似乎是“不行”,但我仍抱有希望,想询问是否有解决办法?(我知道可以排查每个panic的具体原因,但仍认为这个问题有探讨价值。)
简化示例
lib.rs
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; use pyo3::{pymodule, types::PyModule, PyResult, Python}; #[pymodule] fn can_panic(_py: Python, m: &PyModule) -> PyResult<()> { #[pyfn(m)] fn can_panic<'py>( py: Python<'py>, arr: PyReadonlyArray1<i64>, ) -> &'py PyArray1<bool> { let my_arr = arr.as_array(); for v in my_arr { if v == &-1 { panic!("Hey, you can't do that!"); } } vec![true; my_arr.len()].into_pyarray(py) } Ok(()) }
Cargo.toml
[package] name = "can_panic" version = "0.1.0" edition = "2021" [lib] name = "can_panic" crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.18.3", features = ["extension-module", "anyhow"] } numpy = "0.18.0" nalgebra = "0.32.2"
testing.py
import numpy as np import can_panic print("Staring filtering") print(can_panic.can_panic(np.array([1, 2, 3], dtype="int64"))) print("Done filtering") print("Staring Filtering") try: print(can_panic.can_panic(np.array([-1, 2, 3], dtype="int64"))) except Exception: print("didn't work") # call Python equivalent print("Done filtering")
运行输出
Staring filtering [ True True True] Done filtering Staring Filtering thread '<unnamed>' panicked at 'Hey, you can't do that!', src/lib.rs:16:17 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Traceback (most recent call last): File "(snip...)/can_panic/testing.py", line 13, in <module> print(can_panic.can_panic(np.array([-1, 2, 3], dtype="int64"))) pyo3_runtime.PanicException: Hey, you can't do that!
解决方案:在Rust层捕获Panic并转换为Python异常
实际上可以实现Python对Rust panic的捕获,核心是在Rust代码中使用std::panic::catch_unwind捕获panic,再将其转换为PyO3的PyResult错误,这样Python就能通过try-except捕获到对应的异常。
修改后的lib.rs代码如下:
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1}; use pyo3::{pymodule, types::PyModule, PyErr, PyResult, Python}; use std::panic; #[pymodule] fn can_panic(_py: Python, m: &PyModule) -> PyResult<()> { #[pyfn(m)] fn can_panic<'py>( py: Python<'py>, arr: PyReadonlyArray1<i64>, ) -> PyResult<&'py PyArray1<bool>> { // 用catch_unwind包裹可能触发panic的逻辑 let result = panic::catch_unwind(|| { let my_arr = arr.as_array(); for v in my_arr { if v == &-1 { panic!("Hey, you can't do that!"); } } vec![true; my_arr.len()] }); match result { Ok(output_vec) => Ok(output_vec.into_pyarray(py)), // 将panic转换为Python的RuntimeError Err(_) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>( "Rust function panicked", )), } } Ok(()) }
关键说明
- 返回值修改:将原函数的返回类型从
&'py PyArray1<bool>改为PyResult<&'py PyArray1<bool>>,这是PyO3中表示可能出错的标准方式。 - 捕获panic:使用
std::panic::catch_unwind包裹所有可能触发panic的代码块,它会返回一个Result——正常执行时返回Ok(结果),触发panic时返回Err(捕获的panic信息)。 - 转换为Python异常:捕获到panic后,将其转换为Python的
RuntimeError(或其他你需要的异常类型),这样Python的try-except就能捕获到。
修改后的运行结果
重新编译运行测试代码,输出会变成:
Staring filtering [ True True True] Done filtering Staring Filtering didn't work Done filtering
此时Python成功捕获到异常,执行了回退逻辑。
注意事项
catch_unwind只能捕获由panic!宏触发的panic,对于未定义行为(比如内存越界、空指针解引用)导致的程序abort无法捕获。但大部分第三方Rust库的panic属于可捕获类型。- 如果需要传递更具体的panic信息,可以从
catch_unwind的Err中提取panic的payload,转换为字符串后传递给Python异常。
内容的提问来源于stack exchange,提问作者Stephen
相关产品推荐
相关产品推荐

