Handling Python exceptions from an exposed C++ function
Hello all, I don't know if the subject makes any sense, but hopefully the description of the problem will make things clearer :) I have a template class which I am exposing to Python with different types instantiated. For the sake of simplicity, let's say it is a Vector<T> class. I have various versions of this class exposed, Vector<int>, Vector<double>, etc. and everything works fine. Recently, I thought it would be nice to have, on the Python side, a version of my Vector class that can hold any type of Python object (a bit like the builtin list class). Initially, I thought I could expose the C++ class Vector<bp::object> and would hope that everything would work automagically. Unfortunately, for my own purposes I need that the objects stored in a Vector have C++ semantics wrt copy constructors. This means that I need to be able to do deep copies of the stored object via their copy constructor, for instance, and this is clearly not the case for bp::object. Moreover, I need to be able, e.g., to negate an object with the unary "-" operator, which is not possible for bp::object. Etc. etc. So what I did at this point was to create a C++ class, which I called "bp_object", that just wraps a bp::object and adds the desired features: class bp_object { // ... // Copy/move ctors, assignments, additional operators, etc. // ... private: bp::object m_object; }; Then I added the necessary (trivial) converters to/from Python, registered the conversion with the Boost.Python mechanism, and exposed the Vector<bp_object> class to Python (note that the bp_object class is *not* exposed or visible from Python). Everything works fine until some Python exception is thrown within the bp_object class. For instance, upon insertion into a Vector of a value, I check from C++ that the value being inserted is not zero. I implemented the comparison operator for bp_object as: bool operator==(const bp_object &other) const { return m_object == other.m_object; } This can result in an exception being thrown from Python (e.g., comparison of a numpy array with zero). What happens in this case is that a bp::error_already_set is thrown and not caught by anything, and the execution terminates. I can catch the error_already_set in the comparison operator, print information about the Python exception that was thrown, etc. but what I clearly need to do here is to stop the execution of the program and somehow get back to the Python interpreter and translate the exception. How would I do that? Does what I am trying to do make any sense or is it unnecessarily complicated? It seems like this kind of usage is a mix between extending and embedding, and I am not sure if this is supported at all. Cheers, Francesco.
On Sun, Nov 16, 2014 at 1:51 PM, Francesco Biscani <bluescarni@gmail.com> wrote:
This can result in an exception being thrown from Python (e.g., comparison of a numpy array with zero). What happens in this case is that a bp::error_already_set is thrown and not caught by anything, and the execution terminates.
I can catch the error_already_set in the comparison operator, print information about the Python exception that was thrown, etc. but what I clearly need to do here is to stop the execution of the program and somehow get back to the Python interpreter and translate the exception.
bp::error_already_set should actually already be doing exactly what you want; the reason it exists is so Boost.Python can catch it and translate it into a Python exception, and any Boost.Python-wrapped function that throws error_already_set should have it caught and translated without having to do anything special. That means your problem is actually one of debugging why that's not working properly. There have been some reports of problems with error_already_set in cases where some C++ compilers/linkers don't recognize an exception as being the same across dynamic library boundaries. I'm not familiar with the details, but that's my best guess as to what's going on. Hopefully that will provide you a starting point for debugging and googling for solutions (or maybe someone else on this list can help diagnose it). Good luck! Jim
Hello Jim, On 16 November 2014 20:23, Jim Bosch <jbosch@astro.princeton.edu> wrote:
On Sun, Nov 16, 2014 at 1:51 PM, Francesco Biscani <bluescarni@gmail.com> wrote:
This can result in an exception being thrown from Python (e.g., comparison of a numpy array with zero). What happens in this case is that a bp::error_already_set is thrown and not caught by anything, and the execution terminates.
I can catch the error_already_set in the comparison operator, print information about the Python exception that was thrown, etc. but what I clearly need to do here is to stop the execution of the program and somehow get back to the Python interpreter and translate the exception.
bp::error_already_set should actually already be doing exactly what you want; the reason it exists is so Boost.Python can catch it and translate it into a Python exception, and any Boost.Python-wrapped function that throws error_already_set should have it caught and translated without having to do anything special.
That means your problem is actually one of debugging why that's not working properly. There have been some reports of problems with error_already_set in cases where some C++ compilers/linkers don't recognize an exception as being the same across dynamic library boundaries. I'm not familiar with the details, but that's my best guess as to what's going on. Hopefully that will provide you a starting point for debugging and googling for solutions (or maybe someone else on this list can help diagnose it).
Thanks for the pointers, I guess I will keep on doing some experiments trying to understand exactly what is going on. I have a Python 3 install available as well, maybe I can try to see if anything changes there. Cheers, Francesco.
participants (2)
-
Francesco Biscani -
Jim Bosch