9

从 Cython 调用时,我在处理自定义 C++ 异常时遇到了一些麻烦。我的情况如下:我有一个CustomLibraryException用于所有异常的库。我想要的基本上是得到错误消息并用它引发 Python 错误。

用户指南有一些提示,但有点不具体。第一种可能性是:

cdef int bar() 除了 +ValueError

这会将 转换CustomLibraryException为 a ValueError,但会丢失错误消息。

另一种可能性是使用显式转换错误

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

我真的不明白这个解决方案。我知道 raise_py_error 必须是一个以某种方式处理错误的 C++ 函数。我不知道如何处理它。该函数没有参数,而是catch在 C++ 的块内调用。

如果有人有在 Cython 中处理 C++ 异常的工作示例,那将有很大帮助。

4

4 回答 4

9

同意文档页面中的措辞有待改进。虽然“ 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
于 2015-03-12T05:21:20.077 回答
4

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");
  }
}

因此,您可以#define __Pyx_CppExn2PyErr your_custom_exn_handler在包含的 .h 文件中覆盖通用行为,或者使用一次性自定义处理程序作为

cdef extern from "...":
    void your_exn_throwing_fcn() except +your_custom_exn_handler
于 2012-09-14T15:15:27.773 回答
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());
    }
}

这将导致RuntimeError在 Python 端引发 a。或者,throwan std::invalid_argumentto raiseaValueError等(请参阅您链接到的页面中的表格)。

于 2012-05-21T12:57:06.557 回答
1

在 Cython 的来源中,https://github.com/cython/cython/blob/master/tests/run/cpp_exceptions.pyx他们实际上是raise_py_error在 .pyx 文件中实现的。这使得在其他 .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在所有文件中添加一个。

但是,这“丢失”了e.what()C++ 异常。所以一个更有趣的解决方案需要更多的帮助文件:

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)
于 2018-03-12T23:07:23.273 回答