boost :: python Экспорт пользовательских исключений - PullRequest
23 голосов
/ 14 февраля 2010

В настоящее время я пишу расширение C ++ для Python, используя Boost.Python. Функция в этом расширении может генерировать исключение, содержащее информацию об ошибке (за исключением просто читаемой человеком строки, описывающей то, что произошло). Я надеялся, что смогу экспортировать это исключение в 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?

Ответы [ 3 ]

26 голосов
/ 14 февраля 2010

Решение состоит в том, чтобы создать ваш класс исключений, как любой обычный класс 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
4 голосов
/ 09 марта 2012

Ответ, данный Джеком Эдмондсом, определяет класс исключений Python, который не наследует Exception (или любой другой встроенный класс исключений Python). Так что, хотя это можно поймать с

except my_cpp_extension.MyCPPException as e:
    ...

его нельзя поймать обычным поймать всех

except Exception as e:
    ...

Здесь - как создать собственный класс исключений Python, который делает наследующим Exception.

1 голос
/ 14 июня 2016

Благодаря вариационным шаблонам и обобщенному лямбда-захвату мы можем свернуть ответ Джека Эдмонда в нечто гораздо более управляемое и спрятать всю ненужную информацию от пользователя:

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* и где-то не нужна дополнительная функция.

...