25

我目前正在使用 Boost.Python 为 Python 编写 C++ 扩展。此扩展中的函数可能会生成包含有关错误信息的异常(不仅仅是描述所发生情况的人类可读字符串)。我希望我可以将这个异常导出到 Python,这样我就可以捕获它并使用额外的信息做一些事情。

例如:

import my_cpp_module
try:
    my_cpp_module.my_cpp_function()
except my_cpp_module.MyCPPException, e:
    print e.my_extra_data

不幸的是,Boost.Python 似乎将所有 C++ 异常(即 的子类std::exception)转换为RuntimeError. 我意识到 Boost.Python 允许实现自定义异常转换,但是,需要使用PyErr_SetObject一个PyObject*(用于异常的类型)和一个PyObject*(用于异常的值)——我都不知道如何从我的 Boost 中获取。 Python 类。也许有一种我还没有找到的方法(这会很棒)。否则有谁知道如何导出自定义 C++ 异常以便我可以在 Python 中捕获它?

4

6 回答 6

28

解决方案是像任何普通的 C++ 类一样创建您的异常类

class MyCPPException : public std::exception {...}

诀窍是所有 boost::python::class_ 实例都包含对对象类型的引用,该引用可通过它们的 ptr() 函数访问。你可以在使用 boost::python 注册类时得到这个,如下所示:

class_<MyCPPException> myCPPExceptionClass("MyCPPException"...);
PyObject *myCPPExceptionType=myCPPExceptionClass.ptr();
register_exception_translator<MyCPPException>(&translateFunc);

最后,当您将 C++ 异常转换为 Python 异常时,请执行以下操作:

void translate(MyCPPException const &e)
{
    PyErr_SetObject(myCPPExceptionType, boost::python::object(e).ptr());
}

这是一个完整的工作示例:

#include <boost/python.hpp>
#include <assert.h>
#include <iostream>

class MyCPPException : public std::exception
{
private:
  std::string message;
  std::string extraData;
public:
  MyCPPException(std::string message, std::string extraData)
  {
    this->message = message;
    this->extraData = extraData;
  }
  const char *what() const throw()
  {
    return this->message.c_str();
  }
  ~MyCPPException() throw()
  {
  }
  std::string getMessage()
  {
    return this->message;
  }
  std::string getExtraData()
  {
    return this->extraData;
  }
};

void my_cpp_function(bool throwException)
{
  std::cout << "Called a C++ function." << std::endl;
  if (throwException)
    {
      throw MyCPPException("Throwing an exception as requested.",
               "This is the extra data.");
    }
}

PyObject *myCPPExceptionType = NULL;

void translateMyCPPException(MyCPPException const &e)
{
  assert(myCPPExceptionType != NULL);
  boost::python::object pythonExceptionInstance(e);
  PyErr_SetObject(myCPPExceptionType, pythonExceptionInstance.ptr());
}

BOOST_PYTHON_MODULE(my_cpp_extension)
{
  boost::python::class_<MyCPPException>
    myCPPExceptionClass("MyCPPException",
            boost::python::init<std::string, std::string>());
  myCPPExceptionClass.add_property("message", &MyCPPException::getMessage)
    .add_property("extra_data", &MyCPPException::getExtraData);
  myCPPExceptionType = myCPPExceptionClass.ptr();
  boost::python::register_exception_translator<MyCPPException>
    (&translateMyCPPException);
  boost::python::def("my_cpp_function", &my_cpp_function);
}

这是调用扩展的 Python 代码:

import my_cpp_extension
try:
    my_cpp_extension.my_cpp_function(False)
    print 'This line should be reached as no exception should be thrown.'
except my_cpp_extension.MyCPPException, e:
    print 'Message:', e.message
    print 'Extra data:',e.extra_data

try:
    my_cpp_extension.my_cpp_function(True)
    print ('This line should not be reached as an exception should have been' +
       'thrown by now.')
except my_cpp_extension.MyCPPException, e:
    print 'Message:', e.message
    print 'Extra data:',e.extra_data
于 2010-02-14T20:33:58.150 回答
4

Jack Edmonds 给出的答案定义了一个不继承的 Python“异常”类Exception(或任何其他内置的 Python 异常类)。所以虽然它可以被抓住

except my_cpp_extension.MyCPPException as e:
    ...

它不能用通常的全部捕获

except Exception as e:
    ...

以下是如何创建一个继承的自定义 Python异常Exception

于 2012-03-09T06:48:47.440 回答
1

多亏了可变参数模板和通用的 lambda 捕获,我们可以将Jack Edmond 的答案折叠成更易于管理的东西,并向用户隐藏所有琐碎的内容:

template <class E, class... Policies, class... Args>
py::class_<E, Policies...> exception_(Args&&... args) {
    py::class_<E, Policies...> cls(std::forward<Args>(args)...);
    py::register_exception_translator<E>([ptr=cls.ptr()](E const& e){
        PyErr_SetObject(ptr, py::object(e).ptr());
    });
    return cls;
}

要公开MyCPPException为异常,您只需py::class_将绑定更改为exception_

exception_<MyCPPException>("MyCPPException", py::init<std::string, std::string>())
    .add_property("message", &MyCPPException::getMessage)
    .add_property("extra_data", &MyCPPException::getExtraData)
;

现在我们回到 Boost.Python 的精妙之处:不需要命名class_实例,不需要这个 extra PyObject*,也不需要某个地方的额外函数。

于 2016-06-13T21:36:05.053 回答
0

这是 Jack Edmonds 的解决方案,移植到 Python 3,使用来自here的建议,它本身使用来自here的代码。将它们组装在一起(并对 C++ 代码进行一点现代化改造)给出:

#include <boost/python.hpp>
#include <assert.h>
#include <iostream>

class MyCPPException : public std::exception
{
public:
    MyCPPException(const std::string &message, const std::string &extraData)
        : message(message), extraData(extraData)
    {
    }
    const char *what() const noexcept override
    {
        return message.c_str();
    }
    std::string getMessage() const
    {
        return message;
    }
    std::string getExtraData() const
    {
        return extraData;
    }
private:
    std::string message;
    std::string extraData;
};

void my_cpp_function(bool throwException)
{
    std::cout << "Called a C++ function." << std::endl;
    if (throwException) {
        throw MyCPPException("Throwing an exception as requested.",
                             "This is the extra data.");
    }
}

static PyObject* createExceptionClass(const char* name, PyObject* baseTypeObj = PyExc_Exception)
{
    using std::string;
    namespace bp = boost::python;

    const string scopeName = bp::extract<string>(bp::scope().attr("__name__"));
    const string qualifiedName0 = scopeName + "." + name;
    PyObject* typeObj = PyErr_NewException(qualifiedName0.c_str(), baseTypeObj, 0);
    if (!typeObj) bp::throw_error_already_set();
    bp::scope().attr(name) = bp::handle<>(bp::borrowed(typeObj));
    return typeObj;
}

static PyObject *pythonExceptionType = NULL;

static void translateMyCPPException(MyCPPException const &e)
{
    using namespace boost;
    python::object exc_t(python::handle<>(python::borrowed(pythonExceptionType)));
    exc_t.attr("cause") = python::object(e); // add the wrapped exception to the Python exception
    exc_t.attr("what") = python::object(e.what()); // for convenience
    PyErr_SetString(pythonExceptionType, e.what()); // the string is used by print(exception) in python
}

BOOST_PYTHON_MODULE(my_cpp_extension)
{
    using namespace boost;
    python::class_<MyCPPException>
            myCPPExceptionClass("MyCPPException",
                                python::init<std::string, std::string>());
    myCPPExceptionClass.add_property("message", &MyCPPException::getMessage)
            .add_property("extra_data", &MyCPPException::getExtraData);

    pythonExceptionType = createExceptionClass("MyPythonException");
    python::register_exception_translator<MyCPPException>(&translateMyCPPException);
    python::def("my_cpp_function", &my_cpp_function);
}

和python文件来测试它:

#!/usr/bin/env python3

import my_cpp_extension
try:
    my_cpp_extension.my_cpp_function(False)
    print('This line should be reached as no exception should be thrown.')
except my_cpp_extension.MyPythonException as e:
    print('Message:', e.what)
    print('Extra data:',e.cause.extra_data)

try:
    my_cpp_extension.my_cpp_function(True)
    print ('This line should not be reached as an exception should have been' +
       'thrown by now.')
except my_cpp_extension.MyPythonException as e:
    print('Message:', e.what)
    print('Extra data:',e.cause.extra_data)

并将其作为标准 python 异常捕获也可以:

except Exception as e:
    print('Exception: ',e)
于 2020-12-03T15:36:53.733 回答
0

你在 macOS 上测试过吗?该解决方案在 Linux (gcc) 和 Windows (VS) 中运行良好,但是当我在 macOS Big Sur (Xcode Clang) 中对其进行测试时,我得到以下错误而不是异常:

Called a C++ function.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
SystemError: <Boost.Python.function object at 0x7fdcf5c30700> returned NULL without setting an error
于 2021-09-28T17:10:10.263 回答
0

我结合了 Barry 和 David Faure 的答案并创建了一个工作 python 异常。它提取异常名称的 S 参数(因此它必须显式传递给 class_ 对象)。

template <class E, class... Policies, class S, class... Args>
boost::python::class_<E, Policies...> exception_(S name, Args&&... args) {
    boost::python::class_<E, Policies...> cls(name, std::forward<Args>(args)...);
        
    pythonExceptionType = createExceptionClass(name);
    
    boost::python::register_exception_translator<E>([ptr=pythonExceptionType](E const& e){
        boost::python::object exc_t(boost::python::handle<>(boost::python::borrowed(ptr)));
        exc_t.attr("cause") = boost::python::object(e); 
        exc_t.attr("what") = boost::python::object(e.what());
        PyErr_SetString(ptr, e.what());
        PyErr_SetObject(ptr, boost::python::object(e).ptr());
    });
    return cls;
}

static PyObject* createExceptionClass(const char* name, PyObject* baseTypeObj = PyExc_Exception)
{
    using std::string;
    namespace bp = boost::python;

    const string scopeName = bp::extract<string>(bp::scope().attr("__name__"));
    const string qualifiedName0 = scopeName + "." + name;
    PyObject* typeObj = PyErr_NewException(qualifiedName0.c_str(), baseTypeObj, 0);
    bp::scope().attr(name) = bp::handle<>(bp::borrowed(typeObj));
    return typeObj;
}

BOOST_PYTHON_MODULE(MyModule)

exception_<MyException, bases<SomeBaseException>>("MyException", no_init)
            .def("get_message", &MyException::get_message)
            .def("get_reason", &MyException::get_reason)
            ;

在蟒蛇中

try:
    do_sth()
except MyModule.MyException as e: 
    print(e.cause.get_message())

    print(e.cause.get_reason())
于 2022-01-20T13:02:31.117 回答