I am struggling a bit with the Python C API. I am calling a python method to do some game AI at about 60hz. It works most of the time but every second or so the call to PyEval_CallObject results in a NULL return value. If I correctly detect the error and continue looping, all is well for the next second or so, whereupon the error occurs again.
I suspect I am doing something wrong with ref counting but I can't figure out what it is:
int script_do_ai(struct game_data_t* gd)
{
PyObject *pAiModule, *pResult;
float result=0.0;
pResult = NULL;
pAiModule = PyImport_Import(PyString_FromString("ai_script"));
Yeah, I'm importing the the module every iteration. Is that necessary? If I store pAiModule as a global, I get a hard crash after about a second.
pResult = PyEval_CallObject(PyObject_GetAttrString(pAiModule, "do_ai"),
Py_BuildValue("f", gd->important_float))
if (pResult != NULL)
{
PyArg_Parse(pResult, "f", &result);
Py_DECREF(pResult);
ConquerEnemies(result); //you get the idea
}
else //this happens every 75 or so iterations thru the loop
{
if (PyErr_ExceptionMatches(PyExc_SomeException)) //? not sure what to do here
{
I haven't been able to find out how to extract the exception yet, either...without testing for every exception
}
}
Am I even close to doing this right? Like I said, it mostly works but I'd really like to understand why I am getting an error.
Thank you in advance for any help.
You can call PyImport_Import()
as often as you like, but you'll just keep getting the same module object back. Python caches imports. Also, instead of creating a new Python string and leaking the reference (and thus the object), you should just use PyImport_ImportModule()
, which takes a const char *
.
PyImport_Import*()
return a new reference, though, you should call Py_DECREF()
on it when you're done. Storing the module in a global should not be a problem, as long as you own a reference to it (which you do, here.)
In your call to PyEval_CallObject()
you aren't checking the result of Py_BuildValue()
for errors, and you're also not calling Py_DECREF()
when you're done with it, so you're leaking that object as well.
In order to convert a Python float to a C double, you should probably just call PyFloat_AsDouble()
instead of mucking about with PyArg_Parse()
(and keep in mind to test for exceptions)
Down to the actual error handling: PyErr_ExceptionMatches()
is only useful when you actually want to test if the exception matches something. If you want to know if an exception occurred, or get the actual exception object, PyErr_Occurred()
is what you should call. It returns the current exception type (not the actual exception object) as a borrowed reference, or NULL if none is set. If you want to just print a traceback to stderr, PyErr_Print()
and PyErr_Clear()
are what you want to use. For more fine-grained inspection of the actual error in your code, PyErr_Fetch()
gets you the current exception object and the traceback associated with it (it gets you the same information as sys.exc_info()
in Python code.) All things considered you rarely want to get that deeply into the exception handling in C code.