I recently upgraded a C++ project I'm working on, which embedds python, from Python 3.4.3 to Python 3.5.2 (it is an executable that builds as 32 or 64 bits, and has the same behavior in both versions).
I am creating my own exception:
static PyObject* MyException_type_obj = 0;
void setup(PyObject* module){
MyException_type_obj = PyErr_NewException("my_module.MyException", NULL, NULL);
PyObject* dict = PyModule_GetDict(module);
PyDict_SetItemString(dict, "MyException", MyException_type_obj);
}
Then raising it in some C++ code that is called by python code:
static PyObject* RaiseIt(PyObject* self, PyObject* args)
{
PyErr_Format(Forever_Exception_type_obj, "Exit Requested");
return 0;
}
So far, so good. If I catch it in python, everything is fine:
try:
my_module.raise_it()
except my_module.MyException:
import sys, traceback
exc_type, exc_value, exc_tb = sys.exc_info()
print(traceback.format_exception(exc_type, exc_value, exc_tb)
Prints the correct tracback to the command line.
However, when I try to do a similar thing in C++/Python where I use PyErr_Fetch
:
std::string get_traceback(){
PyObject* type;
PyObject* value;
PyObject* traceback;
PyErr_Fetch(&type, &value, &traceback);
std::string fcn = "";
fcn += "def get_pretty_traceback(exc_type, exc_value, exc_tb):\n";
fcn += " import sys, traceback\n";
fcn += " lines = []\n";
fcn += " lines = traceback.format_exception(exc_type, exc_value, exc_tb)\n";
fcn += " output = '\\n'.join(lines)\n";
fcn += " return output\n";
PyRun_SimpleString(fcn.c_str());
PyObject* mod = PyImport_ImportModule("__main__");
PyObject* method = PyObject_GetAttrString(mod, "get_pretty_traceback");
PyObject* outStr = PyObject_CallObject(method, Py_BuildValue("OOO", type, value, traceback));
std::string pretty = PyBytes_AsString(PyUnicode_AsASCIIString(outStr));
Py_DECREF(method);
Py_DECREF(outStr);
return pretty;
}
It breaks down and I get a null outStr
.
Getting some additional details
fcn += " lines = []\n";
fcn += " try:\n";
fcn += " lines = traceback.format_exception(exc_type, exc_value, exc_tb)\n";
fcn += " except Exception as e:\n";
fcn += " print('Exception while rendering a python exception for C')\n";
fcn += " print(e)\n";
fcn += " output = '\\n'.join(lines)\n";
Gives me the cryptic error message:
'str' object has no attribute '__cause__'
As an aside, this does not happen in python 2.7 either (with minor string-related changes). It is only occouring for me in python 3.5.
This same C++ get_traceback() function works fine with exceptions initiated in python, just not the C++ ones.
Apparently
PyErr_NormalizeException
is needed immediately after myPyErr_Fetch
call, which converts the value from a string to an instance of the exception object.Fixes the issue. I have no idea why the value coming from an exception generated in C++ vs. one from python changes the value, but this takes care of it.