[英]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;
}
}
swig 異常文檔有幫助嗎? 它提到定義不同的異常處理程序..
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.