我正在编写一个SWIG包装器,用于封装一个自定义的C++库,该库定义了自己的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);
%}
第三步: 正如早期的帖子中提到的那样,我们需要一个异常指令-请注意,"%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
另外,如果您正在使用distutils(至少在开始时我建议使用它),则您的setup.py应该具有类似以下代码的内容,否则当_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 上需要编译异常。你的平台可能不需要这个标志; google 有详细信息。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");
}
}
这将在任何支持的脚本语言中生成一个RuntimeError异常,包括Python,在不在其他头文件中加入Python特定内容的情况下。
您需要在想要进行异常处理的调用之前放置此代码。
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
来自Swig文档
%except(python) {
try {
$function
}
catch (RangeError) {
PyErr_SetString(PyExc_IndexError,"index out-of-bounds");
return NULL;
}
}
您也可以使用:
捕获错误: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");
}
throw()
,否则%catches
是不够的。而且,在这种情况下,%catches
是多余的。链接的SWIG文档仅提到使用%catches
指令与函数名一起使用。 - Melebius