【问题标题】:How do I add attributes to a python exception from C++ in swig?如何在 swig 中从 C++ 向 python 异常添加属性?
【发布时间】:2022-01-10 18:35:17
【问题描述】:

我有一个带有自定义异常的 C++ 库,该异常源自 std::runtime_error。它代表了一种断言失败,所以我想将它映射到 python 中的 AssertionError 。到目前为止,这很容易:

%exception {
  try {
    $action
  } catch(MyNamespace::Assertion& e) {
    PyErr_SetString(PyExc_AssertionError, e.what() );
    return nullptr;
  } catch(...) {
    PyErr_SetString(PyExc_RuntimeError, "Unknown Exception");
    return nullptr;
  }
}

这很好用。

问题是我刚刚在 C++ 中向 MyNamespace::Assertion 添加了一个数据成员,我想将它传播到 python 异常。问题(以及与之前已经回答的类似问题不同的原因)是我希望 python 中的异常类型从 AssertionError 派生。如果客户端关心新数据成员,他们可以显式捕获它,但如果客户端不关心,我希望他们能够捕获 AssertionError 并让它工作。

我可以添加我自己的从 AssertionError 派生的异常类型并将 C++ 断言映射到它

%{
    static PyObject* p_MyAssertionError;
%}

%init %{
    p_MyAssertionError = PyErr_NewException("_MyModule.MyAssertionError",PyExc_AssertionError, NULL);
    Py_INCREF(p_MyAssertionError);
    PyModule_AddObject(m, "MyAssertionError", p_MyAssertionError);
%}

%pythoncode %{
  MyAssertionError = _MyModule.MyAssertionError
%}

%exception {
  try {
    $action
  } catch(MyNamespace::Assertion& e) {
    PyErr_SetString(p_MyAssertionError, e.what() );
    return nullptr;
  } catch(...) {
    PyErr_SetString(PyExc_RuntimeError, "Unknown Exception");
    return nullptr;
  }
}

这也很好用。问题是现在我想将新数据成员添加到 python 异常中,但我不知道该怎么做(我对 python API 不是很熟悉)。从我在 PyErr_NewException 的文档中看到的内容来看,第三个参数可以是要添加的属性字典,所以我想这将是解决方案的一部分,但我没有看到任何关于如何做到这一点的示例。文档只是说这个参数通常为NULL。

另一个问题是,如果我使用 PyErr_NewException 创建自己的派生异常类型,如何在 %exception 块上构造一个?我假设我需要使用 PyErr_SetObject() 而不是 PyErr_SetString(),虽然我看到了如何使用 PyErr_SetObject() 创建完全自定义类型的异常的示例,但我没有看到任何创建一个派生自标准异常类型,但具有附加属性。

【问题讨论】:

  • 我感觉我曾经回答过类似的问题,但我现在找不到它,所以再写一个......

标签: python c++ exception swig


【解决方案1】:

正如您所注意到的,您需要在此处稍微帮助 SWIG/Python,以确保 C++ 异常的包装形式具有 Python 异常类型作为基类。完成这项工作的最简单方法是在这里使用 SWIG 的 pyabc 来帮助我们。您还需要做一些扭曲以通过引用进行捕获,但在退出 C++ try/catch 后拥有一个可由 Python 拥有的对象。我在我的代码中为此使用了复制构造函数,它可能来自from this answer originally

%module numbers

%include <std_except.i>
%include <exception.i>
%include <pyabc.i>

// This is important to hook our C++ exception to a Python exception type.
%pythonabc(TooBigException, Exception)

%{
#include <iostream>
#include <typeinfo>
%}

%exception {
    try {
        $action
    }
    catch (const TooBigException& e) {
        // Copy construct and wrap a new instance of TooBigException here for Python
        SWIG_Python_Raise(SWIG_NewPointerObj(
            (new TooBigException(e)),
            SWIGTYPE_p_TooBigException,SWIG_POINTER_OWN),
        "TooBigException", SWIGTYPE_p_TooBigException);
        SWIG_fail;
    }
    catch(const std::exception& ex) {
      std::cerr << typeid(ex).name() << "\n";
    }
}

%inline %{
struct TooBigException: std::exception {
        void blahblah() {
                std::cerr << "blah\n";
        }

        virtual const char* what() const noexcept {return "Your messasge here";}
};


int fact(int n) {
    if (n > 10) throw TooBigException();
    else if (n <= 1) return 1;
    else return n*fact(n-1);
}

%extend TooBigException {
        // Always nice to have:
        const char * __str__() const {
                return $self->what();
        }
}

您实际上可以使用异常说明符让 SWIG 自动为我们生成 try/catch/new 对象块:

%module numbers

%include <std_except.i>
%include <exception.i>
%include <pyabc.i>

// It seems this isn't actually needed with the exception specifiers either
//%pythonabc(TooBigException, Exception)

%{
#include <iostream>
#include <typeinfo>
%}

%inline %{
struct TooBigException: std::exception {
        void blahblah() {
                std::cerr << "blah\n";
        }

        virtual const char* what() const noexcept {return "Your messasge here";}
};

// This is the main change here - an exception specifier:
int fact(int n) throw(TooBigException) {
    if (n > 10) throw TooBigException();
    else if (n <= 1) return 1;
    else return n*fact(n-1);
}
    
%extend TooBigException {
        const char * __str__() const {
                return $self->what();
        }
}

如果您检查生成的 C++ 包装器代码,您会发现 SWIG 使用异常说明符为我们生成了与我们的 catch 块非常相似的内容。

其中任何一个都足以用于此测试:

import numbers
try:
    numbers.fact(11)
except numbers.TooBigException as e:
    print(e)
    print(type(e))
    e.blahblah()

【讨论】:

  • 不错的解决方案。也许您应该将其重命名为不同于数字的名称....
猜你喜欢
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 2014-11-06
  • 1970-01-01
  • 2011-02-13
  • 1970-01-01
  • 2010-11-26
  • 1970-01-01
相关资源
最近更新 更多