[C++-sig] Howto expose exception classes using boost.python?

Ryan Gallagher ryan.gallagher at gmail.com
Tue May 9 20:05:47 CEST 2006


Ryan Gallagher <ryan.gallagher <at> gmail.com> writes:

> 
> David Abrahams <dave <at> boost-consulting.com> writes:
> 
> > Officially, it does require that exceptions be derived from
> > PyExc_Exception (I don't remember where that's documented), but
> > unofficially, you can throw anything :)
> > 
> 
> Does this really work in practice though?  (With classes exposed through 
> Boost.Python at least?) 
> 
> I was also working on this problem several months back, translating an 
> exception class I exposed through Boost.Python using class_<> to raise an 
> instance of the python class.  
> ...

Here's the example output and code I had.  Ignoring the hardcoded module 
name this seemed to be a good solution, except that it doesn't quite work. 
;-)  (Also, I'd prefer to map std::runtime_error to Python's equivalent.)

(What I had recalled as a stack corruption was the infact just the
SystemError exception.)

Here's the python output I had:

Python 2.4.1c1 (#63, Mar 10 2005, 10:36:41) [MSC v.1310 32 bit (Intel)] on win32

Type "help", "copyright", "credits" or "license" for more information.
>>> import pit
>>> dir(pit)
['RuntimeError', '__doc__', '__file__', '__name__', 'must_be_even', 'not_even_ex
ception', 'numeric_error']
>>> pit.must_be_even(6)
>>> pit.must_be_even(7)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
<class 'pit.not_even_exception'>: Numeric Error: must_be_even(int) 7 is not even
!
>>> try:
...     pit.must_be_even(7)
... except pit.not_even_exception, e:
...     print(e)
...
Numeric Error: must_be_even(int) 7 is not even!
>>> try:
...     pit.must_be_even(7)
... except pit.numeric_error, e:
...     print(e)
...
Traceback (most recent call last):
  File "<stdin>", line 4, in ?
SystemError: 'finally' pops bad exception
>>> try:
...     pit.must_be_even(7)
... except pit.numeric_error, e:
...     print('1 ')
... except pit.not_even_exception, e:
...     print('2')
...
2

For this wrapper code: ##########################################

#include <boost/python.hpp>

using namespace boost::python;

#include <stdexcept>
#include <iostream>
#include <string>
#include <boost/function.hpp>
#include <boost/format.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>


struct numeric_error : std::runtime_error
{
   numeric_error(std::string const& msg)
      : std::runtime_error(boost::str(boost::format("Numeric Error: %s") % msg))
   {}
};

   
struct not_even_exception : numeric_error
{
   not_even_exception(int i, std::string extra_message)
      : numeric_error(boost::str( boost::format("%s %d is not even!") 
                                % extra_message % i))
      , m_i(i)
      , m_msg(extra_message)
   {}

   int m_i;
   std::string m_msg;
};

void must_be_even(int i)
{
   if(i % 2 != 0)
   {
      throw not_even_exception(i, "must_be_even(int)");
   }
}


namespace {
   template<typename T>
   std::string wrap_output(T const& exn)
   {
      return exn.what();
   }
}

namespace wrap
{
   template< typename CPP_ExceptionType
           , typename X1 = ::boost::python::detail::not_specified
           , typename X2 = ::boost::python::detail::not_specified
           , typename X3 = ::boost::python::detail::not_specified
           >
   class exception 
      : public ::boost::python::class_<CPP_ExceptionType, X1, X2, X3>
   {
   public:
      typedef ::boost::python::class_<CPP_ExceptionType, X1, X2, X3> base_type;
      typedef exception<CPP_ExceptionType, X1, X2, X3>               self;

      // Construct with the class name, with or without docstring, and default 
      // __init__() function
      exception(char const* name, char const* doc = 0)
         : base_type(name, doc), m_exception_name(name)
      {
         init();
      }

      // Construct with class name, no docstring, and an uncallable 
      // __init__ function
      exception(char const* name, no_init_t const& no_init_tag)
         : base_type(name, no_init_tag), m_exception_name(name)
      {
         init();
      }

      // Construct with class name, docstring, and an uncallable 
      // __init__ function
      exception(char const* name, char const* doc, no_init_t const& no_init_tag)
         : base_type(name, doc, no_init_tag), m_exception_name(name)
      {
         init();
      }

      // Construct with class name and init<> function
      template <class DerivedT>
      inline exception(char const* name, init_base<DerivedT> const& i)
         : base_type(name, i), m_exception_name(name)
      {
         init();
      }

      // Construct with class name, docstring and init<> function
      template <class DerivedT>
      inline exception( char const* name
                      , char const* doc
                      , init_base<DerivedT> const& i)
         : base_type(name, doc, i), m_exception_name(name)
      {
         init();
      }

   private:
      std::string get_module_qualified_name() const
      {
         return boost::str(boost::format("%s.%s") % "pit" % m_exception_name);
      }

      void init() const
      {
         using namespace boost;
         function<void (typename base_type::wrapped_type const&)> 
            conversion_func = bind( &exception::to_python_exception
                                  , *this, get_module_qualified_name(), _1);
         ::boost::python::register_exception_translator<typename
             base_type::wrapped_type>(conversion_func);
      }

      static void to_python_exception( ::boost::python::object const& exn_type
                                  , std::string const& exception_name
                                  , typename base_type::wrapped_type const& exn
                                  )
      {
         static const ::boost::python::to_python_value<typename 
           base_type::wrapped_type> convert_argument;
         PyErr_SetObject(exn_type.ptr(), convert_argument(exn));

         throw_error_already_set();
      }

      std::string const m_exception_name;
   };
}


BOOST_PYTHON_MODULE(pit)
{
   wrap::exception<std::runtime_error>("RuntimeError", init<std::string>())
      .def("__str__", &std::runtime_error::what)
      ;

   wrap::exception<numeric_error, bases<std::runtime_error> >
     ("numeric_error", init<std::string>())
      .def("__str__", &wrap_output<numeric_error>)
      ;

   wrap::exception<not_even_exception, bases<numeric_error> > 
      ("not_even_exception", init<int, std::string>())
      .def("__str__", &wrap_output<not_even_exception>)
      ;

   def( "must_be_even"
      , &must_be_even
      , "raises not_even_exception if argument is not even.");

}






More information about the Cplusplus-sig mailing list