Throwing a type defined as a class_ #953
Unanswered
Walter-Reactor
asked this question in
Q&A
Replies: 1 comment 1 reply
-
You can't directly throw a bound nanobind type, because nanobind instances do not have a memory layout that is compatible with Python exception instances. This falls into the category of "nanobind doesn't support multiple inheritance". The workaround I use is to define a wrapper exception type: nb::handle unexpectedExceptionType;
// This exception type is used to return errors from `std::expected`.
//
// Example:
// try:
// expected = mymod.get_some_expected()
// # expected holds the `.value()` of the `std::expected` object
// except mymod.UnexpectedException as e:
// # e.value holds the `.error()` of the `std::expected` object
//
struct UnexpectedException {
const char* what() const { return "<unexpected>"; }
};
template <typename T, typename E>
struct nanobind::detail::type_caster<std::expected<T, E>> {
using Expected = std::expected<T, E>;
using ValueConv = make_caster<T>;
using ErrorConv = make_caster<E>;
// converting Python values to std::exception not supported (haven't found a need for it)
bool from_python(handle, uint8_t, cleanup_list*) noexcept = delete;
template <typename Source>
static handle from_cpp(Source&& src,
rv_policy policy,
cleanup_list* cleanup) noexcept {
policy = infer_policy<Source>(policy);
if (src.has_value()) {
return ValueConv::from_cpp(*std::forward<Source>(src), policy,
cleanup);
} else {
auto error = steal(ErrorConv::from_cpp(
std::forward<Source>(src).error(), policy, cleanup));
PyErr_SetObject(unexpectedExceptionType.ptr(), error.ptr());
return handle();
}
}
NB_TYPE_CASTER(Expected, ValueConv::Name);
};
NB_MODULE(mymod, m) {
unexpectedExceptionType = nb::exception<UnexpectedException>(m, "UnexpectedException", PyExc_ValueError);
// ... rest of bindings ...
} |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
I'm attempting to create idiomatic bindings for a library that makes extensive use of a
std::expected<T,E>
-like type (which may eventually be switched to just be std::expected).The intuitive approach, to me, would be to cause the functions which return these values to raise
E
. However, in my case, E is a type which may not be exclusively used as an exception, so it doesn't make sense to bind it usingnb::exception
. I've explored usingnb::register_exception_translator
, and it shows some promise, however I can't figure out how to allow it to returnE
with the proper type.I think something like
PyErr_SetObject(nb::type<MyType>().ptr(), nb::cast(e).ptr());
would be what I want, but this fails due toMyType
not being derived from BaseException.Is there maybe some extra decoration that could be passed to the class_ definition to make it have the appropriate base on the python side?
The only alternative approach I can think of would be to attempt exposing the interface of the std::expected-like type itself, but that would result in relatively non-pythonic code with a lot of
if x.err():
style code.Beta Was this translation helpful? Give feedback.
All reactions