从Cython调用时,我在处理自定义C ++异常时遇到一些问题。
我的情况如下:我有一个库,使用CustomLibraryException
来表示所有异常。我想要的基本上是获取错误消息并用它引发Python错误。
user guide有一些提示,但有点不明确。 第一种可能性是:
cdef int bar()除了+ ValueError
这会将CustomLibraryException
转换为ValueError
,但会丢失错误消息。
另一种可能性是使用
显式转换错误cdef int raise_py_error()
cdef int something_dangerous() except +raise_py_error
我真的不明白这个解决方案。我知道raise_py_error必须是一个以某种方式处理错误的C ++函数。我不知道如何处理它。该函数没有得到参数,并在C ++中的catch
块内调用。
如果任何人有一个在Cython中处理C ++异常的工作示例,那将会有很大的帮助。
答案 0 :(得分:7)
同意文档页面中的措辞留下了一些不足之处。虽然" Cython不能抛出C ++异常"但这里有一个raise_py_error,可以满足我们的需求。
首先,在cython中定义自定义异常类,并使用" public"来处理它。关键字
from cpython.ref cimport PyObject
class JMapError(RuntimeError):
pass
cdef public PyObject* jmaperror = <PyObject*>JMapError
然后编写异常处理程序(文档不是非常清楚,必须用C ++编写并导入):
#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() );
}
}
最后,将处理程序带入带有extern块的cython,并使用它:
cdef extern from "jmap/cy_utils.H":
cdef void raise_py_error()
void _connect "connect"() except +raise_py_error
完成。我现在看到新的异常,使用错误代码构建:
JMapError: 520 timed connect failed: Connection refused
答案 1 :(得分:3)
Cython中的默认C ++异常处理程序应该准确说明如何完成您要执行的操作:
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");
}
}
因此,您可以在包含的.h文件中#define __Pyx_CppExn2PyErr your_custom_exn_handler
覆盖通用行为,或者使用一次性自定义处理程序
cdef extern from "...":
void your_exn_throwing_fcn() except +your_custom_exn_handler
答案 2 :(得分:2)
如果CustomLibraryException
派生自std::runtime_error
(作为一个表现良好的C ++异常应该),那么您所看到的行为是Cython中的一个错误。
如果没有,那么最简单的方法就是将你正在调用的C ++函数包装在一个转换异常的C ++帮助函数中:
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());
}
}
这将导致在Python端引发RuntimeError
。或者,throw
std::invalid_argument
到raise
和ValueError
等等(请参阅您链接到的页面中的表格)。
答案 3 :(得分:1)
在Cython的来源中,https://github.com/cython/cython/blob/master/tests/run/cpp_exceptions.pyx他们实际上在.pyx文件中实现了raise_py_error
。这使得在其他.pyx文件之间共享错误处理变得更加容易。
快速解决方案只涉及2个文件: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")
和myerror.pxd:
cdef int raise_my_py_error() except *
允许您在所有文件中添加except +my_py_error
。
然而,这“失去了”{+ 1}} C ++异常。因此,一个更有趣的解决方案需要更多辅助文件:
my_error_helper.h:
e.what()
my_error_helper.cxx:
extern const char* get_my_py_error_message();
my_error_helper.pxd:
#include <exception>
const char* get_my_py_error_message()
{
try {
throw;
} catch (const my_custom_cxx_exception& e) {
return e.what();
}
}
my_error.pxd:
cdef extern from "my_error_helper.h":
const char* get_my_py_error_message()
my_error.pyx:
cdef int raise_my_py_error() except *