Динамическое переосмысление самоопределенных исключений C ++ как исключений Python с использованием SWIG

ситуация

Я хочу создать привязку языка Python для C ++ API с помощью SWIG. Некоторые из функций API могут вызывать исключения. Приложение C ++ имеет иерархию самоопределяемых исключений, например, этот пример:

  • IndexError: индекс назначения списка вне диапазона
  • Python: Как игнорировать исключение и продолжить?
  • Python: как я могу узнать, какие исключения могут быть выбраны из вызова метода?
  • Как вы показываете сообщение об ошибке, когда тест не генерирует ожидаемое исключение?
  • Python Когда я поймаю исключение, как мне получить тип, файл и номер строки?
  • Стоимость обработчиков исключений в Python
  • std::exception -> API::Exception -> API::NetworkException -> API::TimeoutException -> API::UnreachableException -> API::InvalidAddressException 

    Желаемое поведение выглядит следующим образом:

    1. Все типы исключений должны иметь соответствующий класс Python в качестве оболочки . Эти классы-оболочки должны быть допустимыми исключениями Python .

    2. Когда вызов API вызывает исключение C ++ , его следует поймать . Должно быть выбрано соответствующее исключение Python (т. Е. Класс-оболочка пойманного исключения C ++).

    3. Это должен быть динамический процесс: тип исключения Python определяется во время выполнения , только на основе типа времени схватки исключенного C ++ исключения. Таким образом, нет необходимости описывать полную иерархию исключений в файле интерфейса SWIG.

    Проблемы и вопросы

    1. Классы Wrapper не исключают Python.

      Хотя SWIG создает классы-оболочки для всех самоопределенных исключений (например, для любого другого класса), эти классы не являются исключениями Python. Обертка базового исключения ( API::Exception в примере) расширяет Object вместо BaseException , класс Python, из которого должны быть выведены все исключения в Python.

      Кроме того, не представляется возможным позволить SWIG добавлять родительский класс вручную. Обратите внимание, что это возможно при использовании SWIG с Java через использование %typemap(javabase) (подробности см. В документации SWIG ).

    2. Как API Python C может вызывать пользовательское исключение?

      Самый распространенный способ выбросить исключение Python из API Python C – это вызвать PyErr_SetString [reference] . Это также показано в демонстрационном приложении ниже.

      Но это только тривиально со стандартными (встроенными) исключениями Python, потому что ссылки на них хранятся в глобальных переменных [ reference ] в API Python C.

      Я знаю, что есть метод PyErr_NewException [reference], чтобы получить ссылки на самоопределенные исключения, но я не получил этого.

    3. Как API Python C API может оценивать тип C ++ во время выполнения, а затем найти соответствующий класс оболочки Python по имени?

      Я предполагаю, что класс Python можно искать по имени во время выполнения, через часть отражения API Python C. Это путь? И как это делается на практике?

    Демо-приложение

    Чтобы поэкспериментировать с этой проблемой, я создал крошечный C ++ API с одной функцией, которая вычисляет факториал числа. Он имеет минимальную самоопределяемую иерархию исключений, состоящую только из одного класса TooBigException .

    Обратите внимание, что это исключение выступает в качестве основного исключения в общей проблеме, и приложение должно работать с любым подклассом. Это означает, что решение может использовать только динамический тип (т. Е. Время выполнения) исключенного захвата для повторного его преобразования в Python (см. Ниже).

    Полный исходный код демонстрационного приложения выглядит следующим образом:

     // File: numbers.h namespace numbers { int fact(int n); } // File: numbers.cpp #include "TooBigException.h" namespace numbers { int fact(int n) { if (n > 10) throw TooBigException("Value too big", n); else if (n <= 1) return 1; else return n*fact(n-1); } } // File: TooBigException.h namespace numbers { class TooBigException: public std::exception { public: explicit TooBigException(const std::string & inMessage, const int inValue); virtual ~TooBigException() throw() {} virtual const char* what() const throw(); const std::string & message() const; const int value() const; private: std::string mMessage; int mValue; }; } // File: TooBigException.cpp #include "TooBigException.h" namespace numbers { TooBigException::TooBigException(const std::string & inMessage, const int inValue): std::exception(), mMessage(inMessage), mValue(inValue) { } const char* TooBigException::what() const throw(){ return mMessage.c_str(); } const std::string & TooBigException::message() const { return mMessage; } const int TooBigException::value() const { return mValue; } } 

    Чтобы получить привязку Python, я использую следующий файл интерфейса SWIG:

     // File: numbers.i %module numbers %include "stl.i" %include "exception.i" %{ #define SWIG_FILE_WITH_INIT #include "TooBigException.h" #include "numbers.h" %} %exception { try { $action } catch (const numbers::TooBigException & e) { // This catches any self-defined exception in the exception hierarchy, // because they all derive from this base class. <TODO> } catch (const std::exception & e) { SWIG_exception(SWIG_RuntimeError, (std::string("C++ std::exception: ") + e.what()).c_str()); } catch (...) { SWIG_exception(SWIG_UnknownError, "C++ anonymous exception"); } } %include "TooBigException.h" %include "numbers.h" 

    Таким образом, каждый вызов API завершается блоком try-catch. Первые исключения нашего базового типа пойманы и обработаны. Затем все остальные исключения захватываются и восстанавливаются с использованием библиотеки исключений SWIG.

    Обратите внимание, что любой подкласс numbers::TooBigException улавливается, и должны быть брошены оболочки их динамического типа (т.е. времени выполнения) , а не оболочки их статического (т. TooBigException компиляции), который всегда является TooBigException !

    Проект можно легко построить, выполнив следующие команды на машине Linux:

     $ swig -c++ -python numbers.i $ g++ -fPIC -shared TooBigException.cpp numbers.cpp numbers_wrap.cxx \ -I/usr/include/python2.7 -o _numbers.so 

    Текущая реализация

    Моя текущая реализация все еще (успешно) выдает фиксированное стандартное исключение Python. Затем код <TODO> заменяется на:

     PyErr_SetString(PyExc_Exception, (std::string("C++ self-defined exception ") + e.what()).c_str()); return NULL; 

    Что дает следующее (ожидаемое) поведение в Python:

     >>> import numbers >>> fact(11) Traceback (most recent call last): File "<stdin>", line 1, in <module> Exception: C++ self-defined exception Value too big 

  • Python: как я могу узнать, какие исключения могут быть выбраны из вызова метода?
  • Чтение Python Regex в комментариях стиля c
  • A + B без арифметических операторов, Python vs C ++
  • python bindings, как это работает?
  • Обтекание библиотеки C в Python: C, Cython или ctypes?
  • Почему операции std :: string работают плохо?
  • 2 Solutions collect form web for “Динамическое переосмысление самоопределенных исключений C ++ как исключений Python с использованием SWIG”

    Похоже, кто-то ответил на ваш основной вопрос в списке пользователей swig-user …

     %exception { try { $action } catch (MyException &_e) { SWIG_Python_Raise(SWIG_NewPointerObj( (new MyException(static_cast<const MyException& >(_e))), SWIGTYPE_p_MyException,SWIG_POINTER_OWN), "MyException", SWIGTYPE_p_MyException); SWIG_fail; } } 

    Я полагаю, это предполагает, что вы создали обертки для ваших классов исключений.

    Пример для вашей иерархии

     std::exception -> API::Exception -> API::NetworkException -> API::TimeoutException -> API::UnreachableException -> API::InvalidAddressException 

    example.i:

     %module example %include "stl.i" %include "exception.i" %{ #define SWIG_FILE_WITH_INIT #include "example.cpp" %} %{ #define CATCH_PE(Namespace,Exception) \ catch(const Namespace::Exception &e) \ { \ SWIG_Python_Raise(SWIG_NewPointerObj(new Namespace::Exception(e), \ SWIGTYPE_p_##Namespace##__##Exception,SWIG_POINTER_OWN), \ #Exception, SWIGTYPE_p_##Namespace##__##Exception); \ SWIG_fail; \ } \ /**/ // should be in "derived first" order #define FOR_EACH_EXCEPTION(ACTION) \ ACTION(API,UnreachableException) \ ACTION(API,TimeoutException) \ ACTION(API,InvalidAddressException) \ ACTION(API,NetworkException) \ ACTION(API,Exception) \ /**/ // In order to remove macros, need traits: // http://swig.10945.n7.nabble.com/traits-based-access-to-swig-type-info-td12315.html %} %exception { try { $action } FOR_EACH_EXCEPTION(CATCH_PE) catch (const std::exception & e) { SWIG_exception(SWIG_RuntimeError, (std::string("C++ std::exception: ") + e.what()).c_str()); } catch (...) { SWIG_exception(SWIG_UnknownError, "C++ anonymous exception"); } } %include "example.cpp" 

    example.cpp:

     #include <exception> #include <stdexcept> namespace API { struct Exception: std::exception { virtual const char* what() const throw() { return "It is API::Exception"; } }; struct NetworkException: Exception { virtual const char* what() const throw() { return "It is API::NetworkException"; } }; struct TimeoutException: NetworkException { virtual const char* what() const throw() { return "It is API::TimeoutException"; } }; struct UnreachableException: NetworkException { virtual const char* what() const throw() { return "It is API::UnreachableException"; } }; struct InvalidAddressException: Exception { virtual const char* what() const throw() { return "It is API::InvalidAddressException"; } }; inline void select(int i) { switch(i) { case 0: throw Exception(); case 1: throw NetworkException(); case 2: throw TimeoutException(); case 3: throw UnreachableException(); case 4: throw InvalidAddressException(); default: throw std::runtime_error("It is std::runtime_error"); } } } 

    Телосложение:

     swig -c++ -python example.i && g++ -fPIC -shared -lpython2.7 example.cpp example_wrap.cxx -I/usr/include/python2.7 -o _example.so 

    test.py:

     #!/usr/bin/env python2.7 from exceptions import BaseException from example import * def catch(i): try: select(i) except UnreachableException as e: print "Caught UnreachableException" print e.what() print e except TimeoutException as e: print "Caught TimeoutException" print e.what() print e except InvalidAddressException as e: print "Caught InvalidAddressException" print e.what() print e except NetworkException as e: print "Caught NetworkException" print e.what() print e except Exception as e: print "Caught Exception" print e.what() print e except BaseException as e: print "Caught BaseException" print str(e) print "_"*16 for i in xrange(6): catch(i) 

    Выход:

     Caught Exception It is API::Exception <example.Exception; proxy of <Swig Object of type 'API::Exception *' at 0x7f9f54a02120> > ________________ Caught NetworkException It is API::NetworkException <example.NetworkException; proxy of <Swig Object of type 'API::NetworkException *' at 0x7f9f54a02120> > ________________ Caught TimeoutException It is API::TimeoutException <example.TimeoutException; proxy of <Swig Object of type 'API::TimeoutException *' at 0x7f9f54a02120> > ________________ Caught UnreachableException It is API::UnreachableException <example.UnreachableException; proxy of <Swig Object of type 'API::UnreachableException *' at 0x7f9f54a02120> > ________________ Caught InvalidAddressException It is API::InvalidAddressException <example.InvalidAddressException; proxy of <Swig Object of type 'API::InvalidAddressException *' at 0x7f9f54a02120> > ________________ Caught BaseException C++ std::exception: It is std::runtime_error ________________ 

    На основании ответа в maillist .

    Python - лучший язык программирования в мире.