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
Uh oh!
There was an error while loading. Please reload this page.
-
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