【问题标题】:Handling custom C++ exceptions in Cython在 Cython 中处理自定义 C++ 异常
【发布时间】:2012-05-21 12:08:40
【问题描述】:

从 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++ 异常的工作示例,那将有很大帮助。

【问题讨论】:

    标签: c++ exception-handling cython


    【解决方案1】:

    同意文档页面中的措辞有待改进。虽然“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
    

    【讨论】:

    • 有趣的答案。您知道是否可以让 cython 直接编译该 c++ 文件,还是我需要手动创建一个库然后链接它?我找不到有关此的任何文档...
    • 我发现了。下面是一个最小的错误处理示例,同时保留来自自定义接收的消息:github.com/SintefRaufossManufacturing/python-hcn/blob/master/…
    • 您能否解释一下您是如何准确地(哪个文件扩展名,哪个必要的导入)来声明 cython 类并将其转换为 PyObject 的? (第一个代码块)默认情况下未定义 PyObject 并从 "Python.h" 导入它使 cython 抱怨 Python 对象无法转换为原始类型...
    • 第一个块(.pyx 文件)丢失“来自 cpython.ref cimport PyObject”。我冒昧地对其进行了编辑。
    • 我仍然遗漏了一点:如果 .pyx(定义 JMapError 的那个)与 .pxd(具有 +raise_py_error 的那个)同名,则每个都可以在标准设置下正常工作.py。但是当它们有不同的名称时(比如因为我想从 2 个不同的 pxd 文件中 +raise_py_error),我还没有正确的 setup.py 语法(取决于我使用的,我在模块导入时得到“未定义的符号 jmaperror” , 或编译时双重定义的符号。
    【解决方案2】:

    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
    

    【讨论】:

      【解决方案3】:

      如果 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。或者,throwstd::invalid_argumentraiseValueError 等(请参阅您链接到的页面中的表格)。

      【讨论】:

      • 异常并非如您预期的那样源自std::runtime。感谢您的帮助:) 但这并没有让它变得更好。引发错误的函数是成员函数,我不想更改它们的代码。这是关于我的gco wrappers 并且许可证不允许我重新分发:-/
      • @AndreasMueller:成员函数可以很容易地封装在独立函数中;只需将他们应该操作的对象作为第一个参数传递:void wrapper(Obj &amp;o, int ham) { return o.wrapped(ham); }
      • 是的,我知道,我只是懒得去做;) 顺便说一句,你知道你是 SO 上排名前 100 的海报吗?
      • @AndreasMueller:我并不感到惊讶。我在这里花了太多时间:)
      【解决方案4】:

      在 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

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

      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)
      

      【讨论】:

        猜你喜欢
        • 2021-02-05
        • 1970-01-01
        • 1970-01-01
        • 2011-06-11
        • 2015-07-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-15
        相关资源
        最近更新 更多