【问题标题】:How do I propagate C++ exceptions to Python in a SWIG wrapper library?如何在 SWIG 包装器库中将 C++ 异常传播到 Python?
【发布时间】:2010-11-26 12:57:56
【问题描述】:

我正在围绕自定义 C++ 库编写 SWIG 包装器,该库定义了自己的 C++ 异常类型。该库的异常类型比标准异常更丰富、更具体。 (例如,一个类表示解析错误并具有一组行号。)如何在保留异常类型的同时将这些异常传播回 Python?

【问题讨论】:

    标签: python exception swig


    【解决方案1】:

    我知道这个问题已经有几个星期了,但我在为自己研究解决方案时才发现它。所以我会尝试回答,但我会提前警告它可能不是一个有吸引力的解决方案,因为 swig 接口文件可能比手动编码包装器更复杂。此外,据我所知,swig 文档从不直接处理用户定义的异常。

    假设您想从您的 c++ 代码模块 mylibrary.cpp 中抛出以下异常,并在您的 python 代码中捕获一些错误消息:

    throw MyException("Highly irregular condition...");  /* C++ code */
    

    MyException 是在别处定义的用户异常。

    在开始之前,请记下应该如何在您的 python 中捕获此异常。出于我们的目的,假设您有如下 Python 代码:

    import mylibrary
    try:
        s = mylibrary.do_something('foobar')
    except mylibrary.MyException, e:
        print(e)
    

    我认为这描述了您的问题。

    我的解决方案包括在您的 swig 接口文件 (mylibrary.i) 中添加四个如下内容:

    第 1 步: 在头指令(通常未命名的 %{...%} 块)中添加指向 Python 感知异常的指针的声明,我们将其称为 pMyException。下面的第 2 步将对此进行定义:

    %{
    #define SWIG_FILE_WITH_INIT  /* for eg */
    extern char* do_something(char*);   /* or #include "mylibrary.h" etc  */
    static PyObject* pMyException;  /* add this! */
    %}
    

    第 2 步: 添加一个初始化指令(“m”特别令人震惊,但这正是 swig v1.3.40 目前在其构建的包装文件中需要的) - pMyException 在上面的步骤 1 中声明:

    %init %{
        pMyException = PyErr_NewException("_mylibrary.MyException", NULL, NULL);
        Py_INCREF(pMyException);
        PyModule_AddObject(m, "MyException", pMyException);
    %}
    

    第 3 步: 如前一篇文章所述,我们需要一个异常指令——注意“%except(python)”已被弃用。这会将 c++ 函数“do_something”包装在一个 try-except 块中,该块捕获 c++ 异常并将其转换为上面第 2 步中定义的 python 异常:

    %exception do_something {
        try {
            $action
        } catch (MyException &e) {
            PyErr_SetString(pMyException, const_cast<char*>(e.what()));
            SWIG_fail;
        }
    }
    
    /* The usual functions to be wrapped are listed here: */
    extern char* do_something(char*);
    

    第 4 步: 因为 swig 在 .pyd dll 周围设置了一个 python 包装(一个“影子”模块),所以我们还需要确保我们的 python 代码可以“透视”到 .pyd 文件。以下对我有用,最好直接编辑 swig py 包装器代码:

    %pythoncode %{
        MyException = _mylibrary.MyException
    %}
    

    对原始海报有多大用处可能为时已晚,但也许其他人会发现上面的一些有用的建议。对于小型作业,您可能更喜欢手工编码的 c++ 扩展包装器的简洁性,而不是 swig 接口文件的混乱。


    添加:

    在我上面列出的接口文件中,我省略了标准模块指令,因为我只想描述使异常工作所需的附加内容。但是你的模块行应该是这样的:

    %module mylibrary
    

    此外,您的 setup.py(如果您使用的是 distutils,我建议您至少开始使用)应该具有类似于以下的代码,否则当 _mylibrary 无法识别时,第 4 步将失败:

    /* setup.py: */
    from distutils.core import setup, Extension
    
    mylibrary_module = Extension('_mylibrary', extra_compile_args = ['/EHsc'],
                        sources=['mylibrary_wrap.cpp', 'mylibrary.cpp'],)
    
    setup(name="mylibrary",
            version="1.0",
            description='Testing user defined exceptions...',
            ext_modules=[mylibrary_module],
            py_modules = ["mylibrary"],)
    

    注意编译标志 /EHsc,我在 Windows 上需要它来编译异常。您的平台可能不需要该标志;谷歌有详细信息。

    我已经使用我自己的名称测试了代码,我在此处将其转换为“mylibrary”和“MyException”,以帮助概括解决方案。希望转录错误很少或没有。要点是 %init 和 %pythoncode 指令以及

    static PyObject* pMyException;
    

    在头指令中。

    希望澄清解决方案。

    【讨论】:

    • 这看起来很有希望——我会试试看。顺便说一句,现在还不算太晚,因为现在我们只是在捕获 RuntimeError 并检查错误消息的文本——哎呀。
    • 在您的示例中使用名为“SWIG_fail”的宏替换您在调用“PyErr_SetString”后所做的“返回NULL”更为通用,该宏应该始终有效,即无论返回的数据类型如何。
    【解决方案2】:

    我会在这里补充一点,因为这里给出的例子现在说“%except(python)”是 已弃用...

    您现在(无论如何,从 swig 1.3.40 开始)可以进行完全通用的、独立于脚本语言的翻译。我的例子是:

    %exception { 
        try {
            $action
        } catch (myException &e) {
            std::string s("myModule error: "), s2(e.what());
            s = s + s2;
            SWIG_exception(SWIG_RuntimeError, s.c_str());
        } catch (myOtherException &e) {
            std::string s("otherModule error: "), s2(e.what());
            s = s + s2;
            SWIG_exception(SWIG_RuntimeError, s.c_str());
        } catch (...) {
            SWIG_exception(SWIG_RuntimeError, "unknown exception");
        }
    }
    

    这将在任何受支持的脚本语言中生成 RuntimeError 异常, 包括 Python,而无需在您的其他标头中获取特定于 Python 的内容。

    您需要将此放在需要此异常处理的调用之前。

    【讨论】:

    • +1 ,只需要确保在他/她的 SWIG 接口文件中包含 %include exception.i(引入 SWIG_exception 宏)
    【解决方案3】:

    来自swig documentation

    %except(python) {
    try {
    $function
    }
    catch (RangeError) {
        PyErr_SetString(PyExc_IndexError,"index out-of-bounds");
        return NULL;
    }
    }
    

    【讨论】:

    • 这很接近。它保留了异常的类型,但是如何封装自定义异常类型?也许 SWIG 示例涵盖了这一点。
    【解决方案4】:

    【讨论】:

      【解决方案5】:

      example.h

      struct MyBaseException : public std::runtime_error {
        MyBaseException(const std::string& msg)
          : std::runtime_error{msg} {}
      };
      
      struct MyDerivedException : public MyBaseException {
        MyDerivedException(const std::string& msg)
          : MyBaseException{msg} {}
      };
      
      void foo() { throw MyBaseException{"my base exception"}; }
      void bar() { throw MyDerivedException{"my derived exception"}; }
      void baz() { throw std::runtime_error{"runtime error"}; }
      void qux() { throw 0; }
      

      example.i

      %module example
      %{
      #include "example.h"
      %}
      
      %include <std_string.i>
      %include <exception.i>
      
      %exception {
        try {
          $action
        } catch (const MyDerivedException& e) {
           PyErr_SetString(SWIG_Python_ExceptionType(SWIGTYPE_p_MyDerivedException), e.what());
           SWIG_fail;
        } catch (const MyBaseException& e) {
           PyErr_SetString(SWIG_Python_ExceptionType(SWIGTYPE_p_MyBaseException), e.what());
           SWIG_fail;
        } catch(const std::exception& e) {
          SWIG_exception(SWIG_RuntimeError, e.what());
        } catch(...) {
          SWIG_exception(SWIG_UnknownError, "");
        }
      }
      
      %exceptionclass MyBaseException;
      
      %include "example.h"
      

      test.py

      import example
      
      try:
        example.foo()
      except example.MyBaseException as e:
        print(e)
      
      try:
        example.bar()
      except example.MyDerivedException as e:
        print(e)
      
      try:
        example.baz()
      except RuntimeError as e:
        print(e)
      
      try:
        example.qux()
      except:
        print("unknown error")
      

      python3 test.py

      my base exception
      my derived exception
      runtime error
      unknown error
      

      【讨论】:

        【解决方案6】:

        你也可以使用:

        捕获:http://www.swig.org/Doc3.0/SWIGPlus.html#SWIGPlus_catches

        例子:

        %catches(std::exception, std::string, int, ...);
        

        为每个函数生成一个 try catch 块:

          try {
            result = (namespace::Function *)new namespace::Function ((uint16_t const *)arg1);
          }
          catch(std::exception &_e) {
            SWIG_exception_fail(SWIG_SystemError, (&_e)->what());
          }
          catch(std::string &_e) {
            SWIG_Python_Raise(SWIG_From_std_string(static_cast< std::string >(_e)), "std::string", 0); SWIG_fail;
          }
          catch(int &_e) {
            SWIG_Python_Raise(SWIG_From_int(static_cast< int >(_e)), "int", 0); SWIG_fail;
          }
          catch(...) {
            SWIG_exception_fail(SWIG_RuntimeError,"unknown exception");
          }
        

        【讨论】:

        • -1:这不起作用,Python 崩溃。 %catches 是不够的,除非我在函数声明中指定 throw( )。在这种情况下,%catches 是多余的。链接的 SWIG 文档仅提及使用 %catches 指令和函数名称。
        • %catch 实际上“异常包装”更多 swig 包装的功能然后 %exception.
        猜你喜欢
        • 2021-05-23
        • 1970-01-01
        • 2012-10-22
        • 1970-01-01
        • 2014-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多