PyO3/pyo3 · error
{}
Error message
{} What it means
This is the panic! expansion inside PyO3's error-wrapping macros: after printing the Python exception to stderr and flushing it, the macro raises a Rust panic with the given message ($code). The message text is dynamic ('{}' interpolated from the call site), so the actual string depends on the enclosing macro use — commonly PyResult unwraps in internal C-API shims.
Source
Thrown at src/macros.rs:128
#[doc(hidden)]
macro_rules! py_run_impl {
($py:expr, $($val:ident)+, $code:expr) => {{
use $crate::types::IntoPyDict;
use $crate::conversion::IntoPyObject;
use $crate::BoundObject;
let d = [$((stringify!($val), (&$val).into_pyobject($py).unwrap().into_any().into_bound()),)+].into_py_dict($py).unwrap();
$crate::py_run_impl!($py, *d, $code)
}};
($py:expr, *$dict:expr, $code:expr) => {{
use ::core::option::Option::*;
if let ::core::result::Result::Err(e) = $py.run(&$crate::impl_::alloc::ffi::CString::new($code).unwrap(), None, Some(&$dict)) {
e.print($py);
// So when this c api function the last line called printed the error to stderr,
// the output is only written into a buffer which is never flushed because we
// panic before flushing. This is where this hack comes into place
$py.run(c"import sys; sys.stderr.flush()", None, None)
.unwrap();
::core::panic!("{}", $code)
}
}};
}
/// Wraps a Rust function annotated with [`#[pyfunction]`](macro@crate::pyfunction).
///
/// This can be used with [`PyModule::add_function`](crate::types::PyModuleMethods::add_function) to
/// add free functions to a [`PyModule`](crate::types::PyModule) - see its documentation for more
/// information.
///
/// # Examples
/// ```
/// use pyo3::prelude::*;
/// #[pyfunction]
/// fn add(x: i32, y: i32) -> i32 {
/// x + y
/// }
///View on GitHub (pinned to ac9b6899d3)
Solutions
- Read the Python traceback printed to stderr above the panic — it contains the real failure
- Fix the underlying C-API/Python error reported (bad code string, missing module, wrong arguments)
- Prefer APIs returning PyResult and handle errors instead of the panicking macro paths
- Wrap entry points so Python exceptions are captured before reaching panicking shims
Defensive patterns
Strategy: try-catch
Validate before calling
// validate Python state before invoking ffi-level helpers
if unsafe { pyo3::ffi::PyErr_Occurred() }.is_null() { /* safe to call */ } Try / catch
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| risky_call())).map_err(|e| /* extract payload, log stderr traceback */);
Prevention
- Read the Python traceback printed to stderr above the panic for the root cause
- Prefer PyResult-returning APIs over panicking macro helpers
- Validate Python code/inputs passed to embedded run/call helpers
When it happens
Trigger: Internal PyO3 macros that run C API calls, print the resulting Python error via e.print($py), flush stderr, and then panic with a caller-supplied code; hit when an embedded PyO3 C-API helper fails and the error is converted to a panic instead of a PyResult.
Common situations: Embedding scenarios where ffi-level helpers fail (syntax errors in generated code, missing modules); debugging macros like pyo3's internal error printing; misconfigured embedded interpreter calls.
Related errors
- Attaching a thread to the interpreter is prohibited while a
- Cannot attach to the Python interpreter while it is finalizi
- Attaching a thread to the interpreter is currently prohibite
- Unknown return value from PyDict_SetDefaultRef: {x}
- dictionary changed size during iteration
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/7d63c22cf929d43f.
Report an issue: GitHub.