I'm cleaning up some of our python bindings for public release as part of the openexr distribution, and wanted to update the python bindings of our exception library, Iex, to use boost::python.
The first thing I did was the trivial binding of the class hierarchy out to python, and performed register_exception_translator<> for each type with a translator like: void translateExc(const BaseExc &exc) { PyErr_SetObject(pytype,boost::python::object(exc).ptr()); } where pytype is the pointer to the type object for BaseExc's python binding. This allows instances of the exception types to be raised in c++, translated into python, and caught. However, the problem I'm having is this: to allow instances of the exception types to be raised in python (or in fact to have a try/except/else block finalize properly), the python objects need to be derived from the python Exception type or one of its subclasses. I saw this thread from several years ago: http://mail.python.org/pipermail/cplusplus-sig/2006-April/010320.html but, at least with python 2.6, objects raised from within a script must derive from Exception or else a TypeError will be raised: testing raising existing BaseExc exception object: Traceback (most recent call last): File "testexc.py", line 24, in <module> raise e1 TypeError: exceptions must be classes or instances, not BaseExc So my question is this: how do I bind a c++ type out to boost::python such that the python bindings for the type inherits from a type defined in python? What I've tried so far was to reflect RuntimeError out to c++ in a manner similar to how the other python builtin object types (e.g. list/dict) are handled in boost::python. The reflection was successful, and I was getting correct translation of RuntimeErrors across the python/C boundary (both for raising and passing instances of RuntimeError objects). I didn't particularly expect this to work because of the object layouts, but at least by using the c++ RuntimeError type as the parameter to bases<>, isinstance identified the instances as being derived from RuntimeError, and the instances were raiseable. However, whenever the objects were deallocated, the deallocation would crash: #0 0x0000000000479e9f in _PyWeakref_GetWeakrefCount (head=0x30) at Objects/weakrefobject.c:16 #1 0x000000000047ca50 in PyObject_ClearWeakRefs (object=0x892960) at Objects/weakrefobject.c:898 #2 0x000000000046a2bd in subtype_dealloc (self=0x892960) at Objects/typeobject.c:968 Looking in valgrind, there were unsurprisingly plenty of other errors before the crash as well. :) One other approach that seemed like it may work would be to define the entire exception hierarchy in python, mirroring the c++ hierarchy, and reflect each class individually to c++. That doesn't seem particularly boost-pythonic, so I'm hoping that there's a better way such that I can just add a small fix-up in the base of the bindings for the exception hierarchy (perhaps a custom PyTypeObject?). Any ideas? I've included a condensed example and testcase below: -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; static void translateExc(const Exc &exc) { PyErr_SetObject(pytype,object(exc).ptr()); } static std::string repr(const Exc &exc) { return (boost::format("%s.%s('%s')")%module%name%exc.what()).str(); } }; template <class Exc, class Base> class_<Exc,bases<Base> > registerExc() { class_<Exc,bases<Base> > exc_class(ExcTranslator<Exc>::name,init<const std::string &>()); exc_class .def("__repr__",&ExcTranslator<Exc>::repr) ; ExcTranslator<Exc>::pytype = exc_class.ptr(); register_exception_translator <Exc>(&ExcTranslator<Exc>::translateExc); return exc_class; } 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) } // namespace ExcTest using namespace ExcTest; BOOST_PYTHON_MODULE(exctest) { def("testException", &testException); class_<BaseExc> myExc_class("BaseExc",init<const std::string &>()); myExc_class .def("__str__",&BaseExc::what) .def("__repr__",&ExcTranslator<BaseExc>::repr) ; ExcTranslator<BaseExc>::pytype = myExc_class.ptr(); register_exception_translator <BaseExc>(&ExcTranslator<BaseExc>::translateExc); 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 "done" _______________________________________________ Cplusplus-sig mailing list Cplusplus-sig@python.org http://mail.python.org/mailman/listinfo/cplusplus-sig