You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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(())
}

关键说明

  1. 返回值修改:将原函数的返回类型从&'py PyArray1<bool>改为PyResult<&'py PyArray1<bool>>,这是PyO3中表示可能出错的标准方式。
  2. 捕获panic:使用std::panic::catch_unwind包裹所有可能触发panic的代码块,它会返回一个Result——正常执行时返回Ok(结果),触发panic时返回Err(捕获的panic信息)。
  3. 转换为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 23:57:02