C ++ custom exception handling in Cython

I have some problems handling custom C ++ exceptions when called from Cython. My situation is this: I have a library that uses a CustomLibraryException for all exceptions. I want to get an error message and raise a Python error with it.

There are some tips in the user manual , but this is a little non-specific. First opportunity:

cdef int bar () except + ValueError

This will convert a CustomLibraryException to a ValueError , but lose the error message.

Another possibility is to explicitly convert the error using

 cdef int raise_py_error() cdef int something_dangerous() except +raise_py_error 

I do not really understand this decision. I realized that raise_py_error should be C ++ - a function that somehow handles the error. I'm not sure how to handle this. The function does not receive an argument and is called inside the catch in C ++.

If someone has a working example of handling C ++ exceptions in Cython, this will be very helpful.

+6
source share
4 answers

If a CustomLibraryException comes from std::runtime_error (as the correct C ++ exception should be), then the behavior you see is a bug in Cython.

If this is not the case, then the easiest way is to wrap a C ++ function that you call in a C ++ helper function that throws an exception:

 double foo(char const *, Bla const &); // this is the one we're wrapping double foo_that_throws_runtime_error(char const *str, Bla const &blaref) { try { return foo(str, blaref); } catch (CustomLibraryException const &e) { throw std::runtime_error(e.get_the_message()); } } 

This will raise a RuntimeError on the Python side. Alternatively, throw a std::invalid_argument to raise a ValueError , etc. (See the table on the page you linked to).

+2
source

I agreed that the wording on the document page leaves much to be desired. Although "Cython cannot throw C ++ exceptions," a raise_py_error occurs here, which does what we want.

First, define a custom exception class in cython and make a handle to it using the keyword "public"

 from cpython.ref cimport PyObject class JMapError(RuntimeError): pass cdef public PyObject* jmaperror = <PyObject*>JMapError 

Then write the exception handler (the documents are not very clear, this should be written in C ++ and imported):

 #include "Python.h" #include "jmap/cy_utils.H" #include "jmap/errors.H" #include <exception> #include <string> using namespace std; extern PyObject *jmaperror; void raise_py_error() { try { throw; } catch (JMapError& e) { string msg = ::to_string(e.code()) +" "+ e.what(); PyErr_SetString(jmaperror, msg.c_str()); } catch (const std::exception& e) { PyErr_SetString(PyExc_RuntimeError, e.what() ); } } 

Finally, bring the handler in cython using an external block and use it:

 cdef extern from "jmap/cy_utils.H": cdef void raise_py_error() void _connect "connect"() except +raise_py_error 

Done. Now I see a new exception built with an error code as intended:

 JMapError: 520 timed connect failed: Connection refused 
+7
source

The default C ++ exception handler in Cython should accurately illustrate how to accomplish what you are trying to do:

 static void __Pyx_CppExn2PyErr() { // Catch a handful of different errors here and turn them into the // equivalent Python errors. try { if (PyErr_Occurred()) ; // let the latest Python exn pass through and ignore the current one else throw; } catch (const std::bad_alloc& exn) { PyErr_SetString(PyExc_MemoryError, exn.what()); } catch (const std::bad_cast& exn) { PyErr_SetString(PyExc_TypeError, exn.what()); } catch (const std::domain_error& exn) { PyErr_SetString(PyExc_ValueError, exn.what()); } catch (const std::invalid_argument& exn) { PyErr_SetString(PyExc_ValueError, exn.what()); } catch (const std::ios_base::failure& exn) { // Unfortunately, in standard C++ we have no way of distinguishing EOF // from other errors here; be careful with the exception mask PyErr_SetString(PyExc_IOError, exn.what()); } catch (const std::out_of_range& exn) { // Change out_of_range to IndexError PyErr_SetString(PyExc_IndexError, exn.what()); } catch (const std::overflow_error& exn) { PyErr_SetString(PyExc_OverflowError, exn.what()); } catch (const std::range_error& exn) { PyErr_SetString(PyExc_ArithmeticError, exn.what()); } catch (const std::underflow_error& exn) { PyErr_SetString(PyExc_ArithmeticError, exn.what()); } catch (const std::exception& exn) { PyErr_SetString(PyExc_RuntimeError, exn.what()); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown exception"); } } 

That way you can either #define __Pyx_CppExn2PyErr your_custom_exn_handler in the included .h file to override the general behavior, or use a one-time user-defined handler as

 cdef extern from "...": void your_exn_throwing_fcn() except +your_custom_exn_handler 
+3
source

In Cython sources https://github.com/cython/cython/blob/master/tests/run/cpp_exceptions.pyx they actually implement raise_py_error in a .pyx file. This greatly facilitates the exchange of errors between other .pyx files.

A quick solution includes just 2 files: myerror.pyx:

 class MyError(RuntimeError): "Base class for errors raised from my C++." pass cdef int raise_my_py_error() except *: raise MyError("There was a problem") 

and myerror.pxd:

 cdef int raise_my_py_error() except * 

which allows you to add except +my_py_error to all of your files.

However, this "loses" e.what() C ++ exceptions. Therefore, for a more interesting solution, several auxiliary files are required:

my_error_helper.h:

 extern const char* get_my_py_error_message(); 

my_error_helper.cxx:

 #include <exception> const char* get_my_py_error_message() { try { throw; } catch (const my_custom_cxx_exception& e) { return e.what(); } } 

my_error_helper.pxd:

 cdef extern from "my_error_helper.h": const char* get_my_py_error_message() 

my_error.pxd:

 cdef int raise_my_py_error() except * 

my_error.pyx:

 cimport my_error_helper class MyError(RuntimeError): "Base class for errors raised from my C++." pass cdef int raise_my_py_error() except *: msg = my_error_helper.get_my_py_error_message().decode('utf-8') raise MyError(msg) 
0
source

Source: https://habr.com/ru/post/916247/


All Articles