繁体   English   中英

如何在 SWIG 包装器库中将 C++ 异常传播到 Python?

[英]How do I propagate C++ exceptions to Python in a SWIG wrapper library?

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

我知道这个问题已经有几个星期了,但我是在为自己研究解决方案时才发现的。 所以我会尝试回答,但我会提前警告它可能不是一个有吸引力的解决方案,因为 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;

在标头指令中。

希望能澄清解决方案。

我将在这里添加一点,因为这里给出的示例现在说“%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");
    }
}

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

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

来自swig 文档

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

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

你也可以使用:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM