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

Bar*_*rry 28 python swig exception

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

小智 21

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

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

throw MyException("Highly irregular condition...");  /* C++ code */
Run Code Online (Sandbox Code Playgroud)

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

在开始之前,请记下如何在python中捕获此异常.对于我们这里的目的,假设您有python代码,如下所示:

import mylibrary
try:
    s = mylibrary.do_something('foobar')
except mylibrary.MyException, e:
    print(e)
Run Code Online (Sandbox Code Playgroud)

我认为这描述了你的问题.

我的解决方案涉及对swig接口文件(mylibrary.i)进行四次添加,如下所示:

步骤1:在header指令(通常未命名的%{...%}块)中添加一个指向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! */
%}
Run Code Online (Sandbox Code Playgroud)

第2步:添加初始化指令("m"特别令人震惊,但这就是swig v1.3.40在其构造的包装文件中当前需要的内容) - 在上面的步骤1中声明了pMyException:

%init %{
    pMyException = PyErr_NewException("_mylibrary.MyException", NULL, NULL);
    Py_INCREF(pMyException);
    PyModule_AddObject(m, "MyException", pMyException);
%}
Run Code Online (Sandbox Code Playgroud)

第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*);
Run Code Online (Sandbox Code Playgroud)

第4步:因为swig在.pyd dll周围设置了一个python包装('shadow'模块),我们还需要确保我们的python代码能够"透视"到.pyd文件.以下对我有用,并且最好直接编辑swig py包装器代码:

%pythoncode %{
    MyException = _mylibrary.MyException
%}
Run Code Online (Sandbox Code Playgroud)

对原始海报有很大用处可能为时已晚,但也许其他人会发现上面的建议有些用处.对于小型作业,您可能更喜欢手动编码的c ++扩展包装的清晰度,以避免swig接口文件的混乱.


添加:

在我上面的接口文件列表中,我省略了标准模块指令,因为我只想描述使异常工作所必需的附加内容.但您的模块行应如下所示:

%module mylibrary
Run Code Online (Sandbox Code Playgroud)

另外,你的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"],)
Run Code Online (Sandbox Code Playgroud)

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

我已经使用我自己的名称测试了代码,我在这里将其转换为"mylibrary"和"MyException"以帮助推广解决方案.希望转录错误很少或没有.主要观点是%init和%pythoncode指令

static PyObject* pMyException;
Run Code Online (Sandbox Code Playgroud)

在header指令中.

希望澄清解决方案.


小智 9

我在这里添加一点,因为这里给出的例子现在说"%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");
    }
}
Run Code Online (Sandbox Code Playgroud)

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

您需要需要此异常处理的调用之前放置它.

  • +1,只需要确保在他/她的SWIG接口文件中包含%include exception.i(引入SWIG_exception宏) (2认同)

Gle*_*len 0

来自swig 文档

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