-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bind a custom exception class, with attributes/methods #1281
Comments
Regarding py::register_exception_translator([](std::exception_ptr p) {
try {
if (p) std::rethrow_exception(p);
} catch (const std::system_error &e) {
const int errornum = e.code().value();
PyErr_SetObject(PyExc_IOError, py::make_tuple(errornum, e.what()).ptr());
}
}); In Python 3 IOError is an alias for OSError, and depending on the error code the actual exception raised can be FileNotFoundError, PermissionError or other exceptions derived from OSError. |
ran into this as well. maybe can formalize this hack, where user specifys that a class is an exception, and we register a pure python class derived from both it and Exception that preserves the initializer (super().__init passthrough). then it will pass isinstance() checks but should have all the same functionality as the exposed class. and there's no change the the rest of pybind11 having exceptions that expose some important fields (like an error code or conflicting name or whatever) isn't all that rare my current solution is to:
|
@earonesty - just wanted to say thanks for sketching this workaround. Took us a while to work out the kinks, but we've implemented it successfully and it seems to be working out so far. |
@feltech @earonesty thanks for the great examples. I had some issues following it so I created a minset example here for people to leverage. #include <pybind11/pybind11.h>
#include <pybind11/eval.h>
namespace py = pybind11;
PYBIND11_MODULE(pybind11_example, m)
{
struct BatchElementException : std::runtime_error
{
BatchElementException(std::size_t idx, const std::string& aMessage)
: std::runtime_error(aMessage), mIndex{ idx } {}
std::size_t mIndex;
};
struct DerivedBatchException : BatchElementException
{
using BatchElementException::BatchElementException;
};
py::exec(R"pybind(
class BatchElementException(RuntimeError):
def __init__(self, index: int, aMessage: str):
self.index = index
super().__init__(aMessage)
def get_index(self) -> int:
return self.index
)pybind",
m.attr("__dict__"), m.attr("__dict__"));
// Retrieve a handle the the exception type just created by executing the string literal above.
const py::object exceptionBase = m.attr("BatchElementException");
// Register Derived Variants. Note: any derived classes with more methods will need a new py::exec variant
// note: no need to register BatchElementException since this was done in py::exec
py::register_exception<DerivedBatchException>(m, "DerivedBatchException", exceptionBase);
// Register a function that will translate our C++ exceptions to the
// appropriate Python exception type.
//
// Note that capturing lambdas are not allowed here, so we must
// `import` the exception type in the body of the function.
py::register_exception_translator([](std::exception_ptr p) {
const auto setPyException = [](const char* pyTypeName, const auto& exc) {
const py::object pyClass = py::module_::import("pybind11_example").attr(pyTypeName);
const py::object pyInstance = pyClass(exc.mIndex, exc.what());
PyErr_SetObject(pyClass.ptr(), pyInstance.ptr());
};
// Handle the different possible C++ exceptions, creating the
// corresponding Python exception and setting it as the active
// exception in this thread.
try {
if (p) std::rethrow_exception(p);
}
catch (const DerivedBatchException& exc) {
setPyException("DerivedBatchException", exc);
}
catch (const BatchElementException& exc) {
setPyException("BatchElementException", exc);
}
});
m.def("raise_batch_element_exception", []()
{
throw BatchElementException(10, "HelloWorld");
});
m.def("raise_derived_exception", []()
{
throw DerivedBatchException(20, "GoodbyeWorld");
});
} // pybind11_example.pyi from pybind11_stubgen pybind11_example -o .
from __future__ import annotations
__all__ = ['BatchElementException', 'DerivedBatchException', 'raise_batch_element_exception', 'raise_derived_exception']
class BatchElementException(RuntimeError):
def __init__(self, index: int, aMessage: str):
...
def get_index(self) -> int:
...
class DerivedBatchException(BatchElementException):
pass
def raise_batch_element_exception() -> None:
...
def raise_derived_exception() -> None:
... // example.py
import pybind11_example as py
try:
py.raise_batch_element_exception()
except py.BatchElementException as e:
print(f'Exception {e.__class__.__name__} caught! index={e.get_index()} msg={e}')
try:
py.raise_derived_exception()
except py.DerivedBatchException as e:
print(f'Exception {e.__class__.__name__} caught! index={e.get_index()} msg={e}')
|
Hello,
I asked quickly on gitter (https://gitter.im/pybind/Lobby?at=5a7b7f1c6117191e610a8304),
but I feel like this may take a bit longer to resolve.
I'd like to bind a custom exception class to Python.
This custom exception class has attributes,
which I want to expose to Python.
This is very similar to the
std::system_error
,which has a
code()
member function:If this issue is sorted out,
maybe as a result a
std::system_error
binding could be provided?There was interests shown here:
My issue right now, is how to inherit a Python built-in type, more precisely,
PyExc_RuntimeError
.I'm not yet trying to register a custom translator.
If I attempt to use the code proposed on Gitter:
py::class_<CppException>(m, "PyException", py::reinterpret_borrow<py::object>(PyExc_RuntimeError)) .def(...)
I get an assertion error when importing the module:
I have a little piece of code that I use to understand how things work,
when inheriting a
PyObject *
.If I run this code, everything compiles
even if
FooBase
is not a base class ofFoo
.However, if I run the code, base_value returns
42
, not444
:If I uncomment
struct Foo // : FooBase
=>struct Foo : FooBase
,I get the expected behavior:
It kind of make sense to me,
but I'm wondering what to do if I wanted to use a Python builtin type as a base,
instead of my FooBase class:
How does my
PyFoo
type store the base class data?In case of
FooBase
, the data is stored whenFoo
inheritsFooBase
.If I inherit
PyExc_RuntimeError
, what would be my base class?I don't really want add anything Python to my base class,
so I assume the right thing to do would be to give this base class information
only to the
PyFoo
wrapper.How can I inherit a builtin Python type such as
PyExc_RuntimeError
?Related issue:
The text was updated successfully, but these errors were encountered: