我目前正在使用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中捕获它吗?
答案 0 :(得分:26)
解决方案是像任何普通的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
答案 1 :(得分:4)
Jack Edmonds给出的答案定义了一个Python“异常”类,它不继承Exception
(或任何其他内置的Python异常类)。所以虽然它可以被抓住
except my_cpp_extension.MyCPPException as e:
...
它不能被通常的捕捉所有
抓住except Exception as e:
...
Here是如何创建 继承Exception
的自定义Python异常类。
答案 2 :(得分:1)
感谢可变参数模板和广义lambda捕获,我们可以将Jack Edmond's answer折叠成更易于管理的东西,并隐藏用户的所有信息:
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_
实例命名,不需要额外的PyObject*
,并且在某个地方不需要额外的功能。
答案 3 :(得分:0)
这是杰克·埃德蒙兹(Jack Edmonds)的解决方案,它使用here的建议移植到Python 3,而该建议本身使用的是here的代码。将它们组装在一起(并稍微现代化C ++代码)可以得到:
limit by
和要测试的python文件:
#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)