Reputation: 77
When I call PyRun_SimpleString(...)
in C++ and my Python script contains invalid syntax or an error, I get an error message that gets printed to my console.
How can I stop that error message from getting flushed to the console and instead, get that error message as a string or a const char*
and probably use that string in my own way (display it as a GUI text or something)?
Upvotes: 0
Views: 1223
Reputation: 281486
PyRun_SimpleString
is a simplified interface to PyRun_SimpleStringFlags
, and quoting the docs for PyRun_SimpleStringFlags
:
If there was an error, there is no way to get the exception information.
Also pretty important,
Note that if an otherwise unhandled SystemExit is raised, this function will not return -1, but exit the process, as long as Py_InspectFlag is not set.
You probably don't want that.
You should instead be using PyRun_String
or PyRun_StringFlags
. Probably PyRun_StringFlags
, since it lets you remember the effects of __future__
imports.
PyRun_StringFlags
has the following signature:
PyObject* PyRun_StringFlags(const char *str, int start, PyObject *globals, PyObject *locals, PyCompilerFlags *flags)
str
is the source string to run.
start
is either Py_eval_input
, Py_file_input
, or Py_single_input
, depending on whether you want this to behave like eval
, like running a script, or like running a statement interactively. Py_eval_input
only allows a single expression, the value of which will be returned from the PyRun_StringFlags
call. Py_single_input
does stuff like expression auto-printing, and only allows one (possibly-compound) statement. You probably want Py_file_input
or Py_single_input
(and if you use Py_single_input
, you might want a custom sys.displayhook
).
globals
and locals
are the global and local dicts to use, just like when calling exec
or eval
. You probably want to fetch the __main__
module's __dict__
, like this:
m = PyImport_AddModule("__main__");
if (m == NULL)
/* bad stuff happened */
do_something_about_that();
Py_INCREF(m);
d = PyModule_GetDict(m);
...
/* later, when you're done with m and d */
Py_DECREF(m);
and use d
for both of these arguments.
PyImport_AddModule
and PyModule_GetDict
both return borrowed references, but I've used Py_INCREF
to get a new reference for m
, and Py_DECREF
when we're done with that reference. That's because the reference PyImport_AddModule
returns is borrowed from sys.modules['__main__']
, and it's possible that something could remove __main__
from sys.modules
. (There's no such worry with d
, because that's borrowed from __main__.__dict__
, which cannot be deleted or reassigned as long as __main__
itself survives. As long as our m
reference is safe, d
is safe.)
flags
is a pointer to a struct with one relevant field, an int
representing compiler flags. __future__
imports affect this field. To make sure __future__
statements from one PyRun_StringFlags
call are still active in the next call, you should create a struct somewhere it won't be deallocated or wiped between runs:
PyCompilerFlags flags = {0};
and pass &flags
as this argument. If you don't want to remember __future__
statement effects, you can pass NULL
, or use PyRun_String
, which doesn't have this argument.
PyRun_StringFlags
will return a Python object on success (usually None
, and remember to decref the reference) and NULL on an exception. If there's an exception, you can inspect it through the C-level API for exception handling, particularly PyErr_Fetch
, PyErr_NormalizeException
, and PyException_SetTraceback
.
Running the following while an exception is set:
PyObject *type, *val, *tb;
PyErr_Fetch(&type, &val, &tb);
is sort of the C-level equivalent to entering an except
block and fetching sys.exc_info()
, except that val
might be NULL
or not an instance of type
, and the exception's __traceback__
attribute might not be set. The following:
PyErr_NormalizeException(&type, &val, &tb);
if (tb != NULL) {
PyException_SetTraceback(val, tb);
}
does that normalization, if you need it.
Upvotes: 7