Мы в нашем проекте стараемся использовать исключения для сигнализации об ошибках. Почему? Причин несколько: удобно при отладке - если что-то пошло не так, мы сразу видим где и что произошло и можем перейти непосредственно к исправлению ошибки не теряя времени на отладку; пользователю приходится быть более дисциплинированным - ведь исключение нельзя проигнорировать, в отличие от проверки возвращаемого значения; удобно при разработке - вместо прокидывания кода ошибки через все функции, мы просто кидаем исключение, которое к тому же может быть гораздо информативнее. В результате, мы верим, что весь комплекс pykd + скрипт становится надежным инструментом.
Но разберемся, как же исключения попадают из С++ кода, написанного с помощью Boost::Python в виртуальную машину Python? Будем двигаться итеративно.
Прекрасно, генерировать исключения python теперь удобно. Но я хочу использовать собственный тип исключений. Возможно ли это? В общем да...
Идея простая. Boost::python дает возможность импортировать типы в python программу. Наш план таков: импортируем тип в python, регистрируем функцию-транслятор С++ исключения и в коде функции-трансялтора используем наш тип исключения, который мы ранее импортировали ( и запомнили! ) в python.
Вот на этом мы могли бы остановиться. Могли бы, если бы мир был совершенен. На самом деле, после чуть более тщательного тестирования, мы приходим к заключению, что работают наши исключения не совсем верно. А именно:
не работает инструкция raise MyException - вместо генерации исключения обозначенного типа генерирует питоновское исключение, общий смысл которого в двух словах сводится к тому, что тип MyException не пригоден для генерации исключений. Вторая проблема заключается в том, что при попытке сделать иерархию исключений мы сталкиваемся с тем, что неверно работает фильтрация исключений, а именно: инструкция except BaseException не ловит исключения дочерних типов. Короче говоря, имплементация наших исключений весьма далека не только от идеала, но и просто от обычных python исключений. Будем разбираться.
Теперь остается привести все в божеский вид.
Но со стороны описания модуля выглядит все не так уж и плохо:
Но разберемся, как же исключения попадают из С++ кода, написанного с помощью Boost::Python в виртуальную машину Python? Будем двигаться итеративно.
Итерация 0: Генерируем python исключение
Сгенерировать исключение в ВМ python просто. Для этого служит функция PyErr_SetString. В нее нужно передать указатель на тип исключения и сторку с описанием ошибки. Например так:PyErr_SetString( PyExc_IndexError, "Index out of range" );Не очень то удобно использовать эту конструкцию внтури С++ кода. Хотелось бы кидать С++ исключение и чтобы оно волшебным образом превращалось в python исключение. К счастью, boost::python предоставляет эту возможность.
Итерация 1: транслируем исключение из C++ в python
В общем все довольно просто. Любое исключение, возникшее во время выполнения python программы внутри модуля, использующего boost::python отлавливается. Существует механизм, с помощью которого можно поучаствовать в обработке такого исключения. Для этого необходимо зарегистрировать функцию-транслятор с помощью вызова register_exception_translator. Вот как это делается:// класс исключения, для использования в C++ коде class PyException : public std::exception { public: PyException( PyObject* pyObj, const std::string &desc ) : std::exception( desc.c_str() ), m_typeObj( pyObj ) {} static void exceptionTranslate(const PyException &e ) { PyErr_SetString( e.m_typeObj, e.what() ); } private: PyObject* m_typeObj; }; void myFunc() { throw PyException( PyExc_IndexError, "Index out of range" ); } BOOST_PYTHON_MODULE( my_module ) { // регистрируем транслятор boost::python::register_exception_translator<PyException>( &PyException::exceptionTranslate ); // функция, вызываемая из python программы boost::python::def( "myFunc", &myFunc ); }
Прекрасно, генерировать исключения python теперь удобно. Но я хочу использовать собственный тип исключений. Возможно ли это? В общем да...
Итерация 2: Использование собственного типа исключений.
Идея простая. Boost::python дает возможность импортировать типы в python программу. Наш план таков: импортируем тип в python, регистрируем функцию-транслятор С++ исключения и в коде функции-трансялтора используем наш тип исключения, который мы ранее импортировали ( и запомнили! ) в python.
// класс исключения, для использования в C++ коде class MyException : public std::exception { public: MyException ( const std::string &desc ) : std::exception( desc.c_str() ) {} static void setTypeObject(PyObject *p) { exceptTypeObject = p; python::register_exception_translator<MyException>( &exceptionTranslate ); } private: static PyObject *exceptTypeObject; static void exceptionTranslate(const MyException &e ) { boost::python::object pyExcept(e); PyErr_SetObject( exceptTypeObject , pyExcept.ptr()); } }; void myFunc() { throw MyException( "Something's wrong" ); } BOOST_PYTHON_MODULE( my_module ) { // функция, вызываемая из python программы boost::python::def( "myFunc", &myFunc ); // регистрирем тип исключения и его транслятор MyException::setTypeObject( boost::python::class_<MyException>( "MyException", "MyException error" ).ptr() ); }
Вот на этом мы могли бы остановиться. Могли бы, если бы мир был совершенен. На самом деле, после чуть более тщательного тестирования, мы приходим к заключению, что работают наши исключения не совсем верно. А именно:
не работает инструкция raise MyException - вместо генерации исключения обозначенного типа генерирует питоновское исключение, общий смысл которого в двух словах сводится к тому, что тип MyException не пригоден для генерации исключений. Вторая проблема заключается в том, что при попытке сделать иерархию исключений мы сталкиваемся с тем, что неверно работает фильтрация исключений, а именно: инструкция except BaseException не ловит исключения дочерних типов. Короче говоря, имплементация наших исключений весьма далека не только от идеала, но и просто от обычных python исключений. Будем разбираться.
Итерация 3: разбираемся
Изучив внимательно текст ошибки при попытке возбудить исключение raise MyExceptionException. Кроме того, тут надо сделать небольшой ( самостоятельный ;) ) экскурс в анатомию питонов и изучить вопрос о типах и метатипах. Если в двух словах, то тип в питоне - это такой же объект и ,соответственно, может быть динамически создан вызовом конструктора у некого типа. Вот этот "некий" тип и является метатипом. Покопавшись внутрях boost::python можно придти к заключению, что все типы, которые он импортирует, создаются через один и тот же метаттип. В итоге наш MyException имеет метатип такой же, как и другие типы, импортированные из boost::python. И он не имеет родственных связей с типами питоновских исключений. А это негативно влияет на обработку исключений питоном. Наш дальнейший план каким-то образом унаследовать MyException от питоновского типа Exception. Как же это сделать то????Итерация 4: Как же это сделать то????
Делать то нечего, приходится отказаться от регистрации типов исключений через бустовый class_. Подсмотрев в его исходники, находим место, где создается новый питоновский тип и переписываем его примерно так:using boost::python; // Базовым типом у нас будет PyExc_Exception handle<> basedtype = handle<>(PyExc_Exception); // Всякий объект в питоне имеет свой список свойств dict ob_dict; ob_dict["__doc__"] = "MyException" // Объект в питоне кстате может иметь много пап. Но у нас будет один - самый лучший! tuple ob_bases = make_tuple( basedtype ); // Вот тут весь оргазм: // Py_TYPE(basedtype.get()) - вернет нам тип от объекта PyExc_Exception. Тип типа - это метатип, мы это выясняли // object( boost::python::handle<>(Py_TYPE(basedtype.get()) ) ) - это будет у нас объект меаттипа // Что такоей вызов SomeType( a, b, c) ? - это конструирование объекта этого типа // Теперь собираем пазл во-едино: мы создаем новый тип, который имеет такой же метатип как и PyExc_Exception // и является наследником PyExc_Exception object ob = object( boost::python::handle<>(Py_TYPE(basedtype.get()) ) )( "MyException", ob_bases, ob_dict ); // остается пристроить новорожденного в приличное место, а именно в скоуп модуля scope().attr( "MyException" ) = ob;
Теперь остается привести все в божеский вид.
Итерация 5: божеский вид
Для приведения в божеский вид нам надо написать какой то шаблон, по своим функциям хоть чуть-чуть напоминающий class_. Как минимум, хочется наследования, пусть даже не множественного. В результате, родился такой вот шаблон. Он далек от изящества к сожалению, но у него доброе сердце )).template< class TExcept > struct exceptPyType{ static python::handle<> pyExceptType; }; template< class TExcept, class TBaseExcept = python::detail::not_specified > class exception { public: exception( const std::string& className, const std::string& classDesc ) { python::handle<> basedtype; if ( boost::is_same<TBaseExcept, python::detail::not_specified>::value ) { basedtype = python::handle<>(PyExc_Exception); } else { basedtype = exceptPyType<TBaseExcept>::pyExceptType; } python::dict ob_dict; ob_dict["__doc__"] = classDesc; python::tuple ob_bases = python::make_tuple( basedtype ); python::object ob = python::object( python::handle<>(Py_TYPE(basedtype.get()) ) )( className, ob_bases, ob_dict ); python::scope().attr( className.c_str() ) = ob; exceptPyType<TExcept>::pyExceptType = python::handle<>( ob.ptr() ); python::register_exception_translator<TExcept>( &exceptionTranslate ); } static void exceptionTranslate(const TExcept &e ) { python::object exceptObj = python::object( exceptPyType<TExcept>::pyExceptType )( e.what() ); PyErr_SetObject( exceptPyType<TExcept>::pyExceptType.get(), exceptObj.ptr()); } };
Но со стороны описания модуля выглядит все не так уж и плохо:
BOOST_PYTHON_MODULE( mymodule ) { exception<MyException>( "MyException", "description" ); exception<MyExceptionEx,MyException>( "MyExceptionEx", "description" ); }
Комментариев нет:
Отправить комментарий