python导出自定义异常 我现在正在使用Posith.python编写Python的C++扩展。此扩展中的函数可能会生成一个异常,其中包含有关错误的信息(不仅仅是描述发生了什么的可读字符串)。我希望能够将此异常导出到Python,以便捕获它并使用额外的信息执行某些操作

python导出自定义异常 我现在正在使用Posith.python编写Python的C++扩展。此扩展中的函数可能会生成一个异常,其中包含有关错误的信息(不仅仅是描述发生了什么的可读字符串)。我希望能够将此异常导出到Python,以便捕获它并使用额外的信息执行某些操作,c++,python,exception,boost-python,C++,Python,Exception,Boost Python,例如: import my_cpp_module try: my_cpp_module.my_cpp_function() except my_cpp_module.MyCPPException, e: print e.my_extra_data 不幸的是,Posith.Python似乎将所有C++异常(即代码< STD::异常< /代码>的子类)转换为运行时错误< /C>。我意识到Boost.Python允许实现自定义异常转换,但是,需要使用PyErr\u SetObject

例如:

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

不幸的是,Posith.Python似乎将所有C++异常(即代码< STD::异常< /代码>的子类)转换为<代码>运行时错误< /C>。我意识到Boost.Python允许实现自定义异常转换,但是,需要使用

PyErr\u SetObject
,它接受
PyObject*
(用于异常的类型)和
PyObject*
(用于异常的值)——我都不知道如何从我的Boost.Python类中获取这两种类型。也许有一种方法(这将是伟大的)我只是还没有找到。否则,有人知道如何导出自定义C++异常,以便我可以在Python中捕获它吗?< /P> < P>解决方案是像任何普通C++类< /P>创建异常类。
class MyCPPException : public std::exception {...}
诀窍在于,所有boost::python::class_uu实例都持有对对象类型的引用,可以通过其ptr()函数访问该对象。在使用boost::python注册类时,您可以得到如下结果:

class_<MyCPPException> myCPPExceptionClass("MyCPPException"...);
PyObject *myCPPExceptionType=myCPPExceptionClass.ptr();
register_exception_translator<MyCPPException>(&translateFunc);
下面是一个完整的工作示例:

#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);
}

Jack Edmonds给出的答案定义了一个Python“exception”类,该类不继承
exception
(或任何其他内置Python异常类)。因此,尽管它可能会被抓到

except my_cpp_extension.MyCPPException as e:
    ...
它不能用通常的一网打尽

except Exception as e:
    ...

是如何创建一个自定义的Python异常类,它继承了
异常

多亏了可变模板和广义lambda捕获,我们可以折叠成更易于管理的东西,并向用户隐藏所有瑕疵:

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;
}

现在我们回到Boost.Python的细节上:不需要命名
实例,不需要这个额外的
PyObject*
,也不需要其他函数

以下是Jack Edmonds的解决方案,它移植到Python3,使用的是advice,而advice本身使用的是来自的代码。将它们组装在一起(并对C++代码进行了一点现代化)给出:

将其捕获为标准python异常也很有效:

except Exception as e:
    print('Exception: ',e)

好的问题和答案!它救了我一天!谢谢,太好了!这里也很有用!如果我能投票,我会投票给5X)但是这并不能包装一个现有的C++类,它来自STD::异常…还是我遗漏了什么?如果我不是,你的解决方案并不能真正回答这个问题thread@DanNIERO:从C++到Python导出异常的正常方式不是包装它,而是将它转换成从代码>异常> /代码>派生的Python异常。但是如果是C++边引发异常,那么在Python中捕获那个异常的最好方法是什么?在这里的示例中,我可以捕获从C++代码中抛出的异常。然而,我不能从python内部提出这个异常。我只能抓住它。如果我没有错,在你的解决方案中,你给出了一种从Python中增加C++异常的方法,但是它并没有使Python“意识到”从C++代码中引发的异常。实际上是这样,但它认为它们都是运行时错误。对不起,如果我遗漏了什么,我只是想understand@DanNiero:你不能真的在C++边上抛出一个异常,并在Python端捕获它。必须在C++侧捕获异常,然后调用<代码> pyelyStScist或<代码> pyelyStaseObj/<代码>以引发Python异常。如果您使用的是Boost.Python,那么Boost.Python会自动为您这样做。如果Boosi.Python没有识别C++异常,那么默认情况下它将引发<代码>运行时错误< /代码>。但是,您可以通过安装自己的异常转换器来覆盖该默认值。在那里,您可以将自己的C++异常翻译成自己的Python异常。我尝试了您的解决方案,并在Python端得到以下错误:<代码> StaseReal:ExtExable,而不是BaseExchange子类< /C> >,和<代码> Type Error:不允许从BaseExchange继承的类不允许。Boost.Python V1.61、Python 3.4.same尝试了此解决方案并获得
TypeError:异常必须派生自BaseException
:(回答很好,但它适用于Python 2。将Python代码移植到Python 3后,这会在运行时发生:
SystemError:exception不是BaseException子类
请参阅我的答案,以获取此代码的Python 3版本。
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;
}
exception_<MyCPPException>("MyCPPException", py::init<std::string, std::string>())
    .add_property("message", &MyCPPException::getMessage)
    .add_property("extra_data", &MyCPPException::getExtraData)
;
#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);
}
#!/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)
except Exception as e:
    print('Exception: ',e)