Skip to content

[QUESTION] Using a class as both an exception and a value #2884

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

Closed
mvoelkle-cern opened this issue Mar 3, 2021 · 3 comments
Closed

[QUESTION] Using a class as both an exception and a value #2884

mvoelkle-cern opened this issue Mar 3, 2021 · 3 comments

Comments

@mvoelkle-cern
Copy link
Contributor

mvoelkle-cern commented Mar 3, 2021

Hello,

I am having trouble using a class as both an exception and a value:

struct Error : public std::runtime_error {
    using std::runtime_error::runtime_error;
};

class ErrorHandler {
    virtual void handle(const Error& error) = 0;
};

class Service {
    void doSync() { throw Error(); }
    void doAsync(ErrorHandler& handler) { handler.handle(Error()); }
};

class PyErrorHandler : public ErrorHandler {
    using ErrorHandler::ErrorHandler;
    void handle(const Error& error) override {
        PYBIND11_OVERLOAD_PURE(void, ErrorHandler, handle, error);
    }
};

PYBIND11_MODULE(something, module) {
    py::class_<ErrorHandler, PyErrorHandler>(module, "ErrorHandler");
    py::class_<Service>(module, "Service")
        .def("doSync", &Service::doSync)
        .def("doAsync", &Service::doAsync);
    py::register_exception<Error>(module, "Error");
}
service = something.Service()
try:
    service.doSync()
except something.Error as error:
    print(error)

class ErrorHandler(something.ErrorHandler):
    def handle(self, error):
        print(error)
service.doAsync(ErrorHandler())

Pybind11 fails to call the python handler from c++ with RuntimeError: make_tuple(): unable to convert arguments to Python object.

If I use py::class_<Error>(module, "Error") instead of register_exception, the Error cannot be caught when calling doSync anymore: TypeError: catching classes that do not inherit from BaseException is not allowed.

The workaround I had to use was to recreate a python error instance in PyErrorHandler::handle:
PYBIND11_OVERLOAD_PURE(void, ErrorHandler, handle, py::module_::import("something").attr("Error")(error.what()));

Is there an other way?

@mvoelkle-cern mvoelkle-cern changed the title [QUESTION] [QUESTION] Using a class as both an exception and a value Mar 3, 2021
@YannickJadoul
Copy link
Collaborator

There's a bit of a disconnect between classes and exception types, indeed, so I do wonder if there's much you can do about this.

One solution could be to have a custom caster, which would solve your initial issue (register_exception seems to not register the created exception type with pybind11, such that the standard caster doesn't work).

@mvoelkle-cern
Copy link
Contributor Author

Thanks! The custom caster ended up much more complicated, so I reverted to my initial workaround.

@YannickJadoul
Copy link
Collaborator

Thanks! The custom caster ended up much more complicated, so I reverted to my initial workaround.

That sounds very possible, yes, so I don't think that's a bad move :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants