我正在编写一个定制C ++库的SWIG包装器,它定义了自己的C ++异常类型。库的异常类型比标准异常更丰富,更具体。 (例如,一个类表示解析错误并且有一个行号集合。)如何在保留异常类型的同时将这些异常传播回Python?
答案 0 :(得分:21)
我知道这个问题有几个星期了,但我刚刚发现它,因为我正在研究自己的解决方案。所以我会回答一下,但我会提前警告它可能不是一个有吸引力的解决方案,因为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步: 在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! */
%}
第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包装('shadow'模块),我们还需要确保我们的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;
标题指令中的。
希望澄清解决方案。
答案 1 :(得分: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");
}
}
这会在任何支持的脚本语言中生成RuntimeError异常, 包括Python,而不是在其他标题中获取特定于python的东西。
您需要在之前将放在需要此异常处理的调用之前。
答案 2 :(得分:0)
%except(python) {
try {
$function
}
catch (RangeError) {
PyErr_SetString(PyExc_IndexError,"index out-of-bounds");
return NULL;
}
}
答案 3 :(得分:0)
答案 4 :(得分:0)
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
答案 5 :(得分:-1)
你也可以使用:
抓住: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");
}