[C++-sig] Inheriting from a python base class/exception translation

Nick Rasmussen nick at ilm.com
Wed Nov 9 00:11:54 CET 2011


On Tue, 08 Nov 2011, Jim Bosch wrote:

> In the meantime, I've essentially followed the "parallel pure-Python
> exception hierarchy" method, but you can do some metaprogramming
> tricks to keep from having to actually build the Python hierarchy
> yourself.  In other words, write some Python code that inspects your
> Boost.Python-wrapped C++ exception hierarchy and calls type(...) to
> make corresponding Python types that derive from Exception.  The
> pure-Python exceptions can then be set as class members of the
> Boost.Python-wrapped C++ exceptions, so your translate functions can
> do something like this:
> 
> void translateExc(const BaseExc & exc) {
>     bp::object wrappedExc(exc);
>     bp::object pyExcType = wrappedExc.attr("_pyExcType")
>     // assume the constructor for the Python exception accepts a
>     // C++ exception; useful if the C++ exception has data
>     bp::object pyExc = pyExcType(wrappedExc);
>     PyErr_SetObject(pyExcType.ptr(), pyExc.ptr());
> }
> 
> To finish it off, you could add a from-Python converter that
> converts the pure-Python exception back to the appropriate C++
> exception when it's passed to Boost.Python-wrapped functions, but
> that isn't always necessary.
> 

Thanks, I ended up implementing something along those lines (which
was actually pretty close to what the original Python/C bindings
were doing).  I've got the basic funcionality working (exception
translation, from python conversion, to python conversion), but it
still seems a bit fragile, and only supports exceptions with a
single string.

I've attached the latest iteration of the module and testcase below.
If anyone can suggest any improvements, or a way to get it working
with standard boost wrappings of the c++ types, I'd appreciate it.

-nick

exctestmodule.cpp:

#include <Python.h>
#include <boost/python.hpp>
#include <boost/format.hpp>

using namespace boost::python;

namespace ExcTest {

template <class Exc>
struct ExcTranslator
{
    static PyObject *pytype;
    static const char *module;
    static const char *name;

    // to python
    static PyObject *convert(const Exc &exc)
    {
        return incref(object(handle<>(borrowed(pytype)))(exc.what()).ptr());
    }

    static PyTypeObject *get_pytype()
    {
        return (PyTypeObject *)pytype;
    }

    // from python
    static void *convertible(PyObject *exc)
    {
        if (!PyType_IsSubtype(Py_TYPE(exc),(PyTypeObject *)pytype)) return 0;
        return exc;
    }

    static void construct(PyObject* raw_exc, converter::rvalue_from_python_stage1_data* data)
    {
        object exc(handle<>(borrowed(raw_exc)));
        std::string s = extract<std::string>(exc.attr("__str__")());
        void *storage = ((converter::rvalue_from_python_storage<Exc>*)data)->storage.bytes;
        new (storage) Exc(s);
        data->convertible = storage;
    }

    // translate exception
    static void translate(const Exc &exc)
    {
        PyErr_SetObject(pytype,convert(exc));
    }
};

template <class Exc, class Base>
void
registerExc()
{
    std::string classname = ExcTranslator<Exc>::name;
    std::string module = ExcTranslator<Exc>::module;
    std::string basename = ExcTranslator<Base>::name;
    std::string basemodule = ExcTranslator<Base>::module;

    dict tmpDict;
    tmpDict["__builtins__"] = handle<>(borrowed(PyEval_GetBuiltins()));

    std::string definition;
    if (basemodule != module)
    {
        definition += (boost::format("import %s\n") % basemodule).str();
        basename = (boost::format("%s.%s") % basemodule % basename).str();
    }
    else
    {
        // bind in the base class type into the tmp dict
        tmpDict[basename] = object(handle<>(borrowed(ExcTranslator<Base>::pytype)));
    }

    definition += (boost::format("class %s (%s):\n"
                                 "  def __init__ (self, v=''):\n"
                                 "    super(%s,self).__init__(v)\n"
                                 "  def __repr__ (self):\n"
                                 "    return \"%s.%s('%%s')\"%%(self.args[0])\n")
                   % classname % basename % classname % module % classname).str();

    handle<> tmp(PyRun_String(definition.c_str(),Py_file_input,tmpDict.ptr(),tmpDict.ptr()));
    object exc_class = tmpDict[classname];
    scope().attr(classname.c_str()) = exc_class;
    ExcTranslator<Exc>::pytype = exc_class.ptr();

    // to python
    to_python_converter<Exc,ExcTranslator<Exc>,true>();

    // from python
    converter::registry::push_back(&ExcTranslator<Exc>::convertible,
                                   &ExcTranslator<Exc>::construct,type_id<Exc>());

    // exception translation
    register_exception_translator <Exc>(&ExcTranslator<Exc>::translate);
}

// not registered, but define RuntimeError so that BaseExc can have the appropriate
// python base type
struct RuntimeError {};
template<> PyObject *ExcTranslator<RuntimeError>::pytype   = PyExc_RuntimeError;
template<> const char *ExcTranslator<RuntimeError>::name   = "RuntimeError";
template<> const char *ExcTranslator<RuntimeError>::module = "__builtin__";

struct BaseExc : public std::exception
{
    explicit BaseExc(const std::string &message) : _message(message) {}
    virtual ~BaseExc() throw() {}
    virtual const char *what() const throw() { return _message.c_str(); }
    std::string _message;
};

struct ArgExc : public BaseExc
{
    explicit ArgExc(const std::string &message) : BaseExc(message) {}
    virtual ~ArgExc() throw() {}
};

void
testException (int idx)
{
    if (idx == 1)
        throw ArgExc("ArgExc from c++");
    throw BaseExc("BaseExc from c++");
}

#define PY_DEFINE_EXC(ExcType,ModuleName,ExcName)                     \
template <> PyObject *ExcTranslator<ExcType>::pytype   = 0;           \
template <> const char *ExcTranslator<ExcType>::module = #ModuleName; \
template <> const char *ExcTranslator<ExcType>::name   = #ExcName;

PY_DEFINE_EXC(BaseExc,exctest,BaseExc)
PY_DEFINE_EXC(ArgExc,exctest,ArgExc)

std::string
baseExcString(const BaseExc &exc)
{
    return exc.what();
}

std::string
argExcString(const ArgExc &exc)
{
    return exc.what();
}

BaseExc
makeBaseExc(const std::string &s)
{
    return BaseExc(s);
}

ArgExc
makeArgExc(const std::string &s)
{
    return ArgExc(s);
}

} // namespace ExcTest

using namespace ExcTest;

BOOST_PYTHON_MODULE(exctest)
{
    def("testException", &testException);
    def("baseExcString", &baseExcString);
    def("argExcString", &argExcString);
    def("makeBaseExc", &makeBaseExc);
    def("makeArgExc", &makeArgExc);

    registerExc<BaseExc,RuntimeError>();
    registerExc<ArgExc,BaseExc>();
}

testexc.py:

#!/usr/bin/env python2.6
import exctest
import traceback

print 'testing BaseExc exception creation:'
e1 = exctest.BaseExc('BaseExc from python')
assert str(e1) == 'BaseExc from python'
assert repr(e1) == "exctest.BaseExc('BaseExc from python')"
#assert isinstance(e1,RuntimeError)
#del e1
print '  pass: %s' % (repr(e1))

print 'testing ArgExc exception creation:'
e2 = exctest.ArgExc('ArgExc from python')
assert str(e2) == 'ArgExc from python'
assert repr(e2) == "exctest.ArgExc('ArgExc from python')"
#assert isinstance(e2,RuntimeError)
assert isinstance(e2,exctest.BaseExc)
#del e2
print '  pass: %s' % (repr(e2))

print 'testing raising existing BaseExc exception object:'
try:
   raise e1
except exctest.ArgExc, e:
   traceback.print_exc()
   assert False
except exctest.BaseExc, e:
   print '  pass: %s' % (repr(e))
except:
   traceback.print_exc()
   assert False
else:
   assert False

print 'testing raising existing ArgExc exception object:'
try:
   raise e2
except exctest.ArgExc, e:
   print '  pass: %s' % (repr(e))
except exctest.BaseExc, e:
   traceback.print_exc()
   assert False
except:
   traceback.print_exc()
   assert False
else:
   assert False

print 'testing BaseExc exception translation:'
try:
   exctest.testException(0)
except exctest.ArgExc, e:
   traceback.print_exc()
   assert False
except exctest.BaseExc, e:
   print '  pass: %s' % (repr(e))
except:
   traceback.print_exc()
   assert False
else:
   assert False

print 'testing ArgExc exception translation:'
try:
   exctest.testException(1)
except exctest.ArgExc, e:
   print '  pass: %s' % (repr(e))
except exctest.BaseExc, e:
   traceback.print_exc()
   assert False
except:
   traceback.print_exc()
   assert False
else:
   assert False


print 'testing BaseExc raise:'
try:
   raise exctest.BaseExc('new BaseExc from python')
except exctest.ArgExc, e:
   traceback.print_exc()
   assert False
except exctest.BaseExc, e:
   print '  pass: %s' % (repr(e))
except:
   traceback.print_exc()
   assert False
else:
   assert False

print 'testing ArgExc raise:'
try:
   raise exctest.ArgExc('new ArgExc from python')
except exctest.ArgExc, e:
   print '  pass: %s' % (repr(e))
except exctest.BaseExc, e:
   traceback.print_exc()
   assert False
except:
   traceback.print_exc()
   assert False
else:
   assert False

print 'testing exception conversion:'
be = exctest.makeBaseExc('testStr')
assert (isinstance(be,exctest.BaseExc))
assert (be.__class__ == exctest.BaseExc)
ae = exctest.makeArgExc('testStr')
assert (isinstance(ae,exctest.BaseExc))
assert (isinstance(ae,exctest.ArgExc))
assert (ae.__class__ == exctest.ArgExc)
assert (exctest.baseExcString(be) == 'testStr')
assert (exctest.baseExcString(ae) == 'testStr')
assert (exctest.argExcString(ae) == 'testStr')

print "done"



More information about the Cplusplus-sig mailing list