PyEval_CallObject иногда не работает в цикле
-
19-09-2019 - |
Вопрос
Я немного борюсь с API Python C.Я вызываю метод Python для выполнения игрового AI на частоте около 60 Гц.Оно работает большинство времени, но примерно каждую секунду вызов PyEval_CallObject приводит к возвращаемому значению NULL.Если я правильно обнаружу ошибку и продолжу цикл, в следующую секунду или около того все будет хорошо, после чего ошибка возникнет снова.
Подозреваю, что делаю что-то не так с подсчетом ссылок, но не могу понять, что именно:
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"));
Да, я импортирую модуль на каждой итерации.Это необходимо?Если я сохраню pAiModule как глобальный файл, примерно через секунду произойдет резкий сбой.
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
{
Мне еще не удалось узнать, как извлечь исключение... без тестирования каждый исключение
}
}
Я хотя бы близок к тому, чтобы сделать это, верно?Как я уже сказал, в основном это работает, но мне бы очень хотелось понять, почему я получаю ошибку.
Спасибо заранее за любую помощь.
Решение
Вы можете позвонить PyImport_Import()
так часто, как вам нравится, но вы просто будете продолжать получать обратно тот же объект модуля.Импорт кэшей Python.Кроме того, вместо создания новой строки Python и утечки ссылки (и, следовательно, объекта) вам следует просто использовать PyImport_ImportModule()
, что занимает const char *
.
PyImport_Import*()
вернуть новую ссылку, однако вам следует позвонить Py_DECREF()
на нем, когда закончишь.Сохранение модуля в глобальном масштабе не должно быть проблемой, если у вас есть ссылка на него (что вы и делаете здесь).
В вашем звонке PyEval_CallObject()
вы не проверяете результат Py_BuildValue()
на ошибки, и вы тоже не звоните Py_DECREF()
когда вы закончите с этим, вы также потеряете и этот объект.
Чтобы преобразовать число с плавающей точкой Python в двойное число C, вам, вероятно, следует просто вызвать PyFloat_AsDouble()
вместо того, чтобы возиться с PyArg_Parse()
(и не забудьте проверить наличие исключений)
Вплоть до фактической обработки ошибок: PyErr_ExceptionMatches()
полезно только тогда, когда вы действительно хотите проверить, соответствует ли исключение чему-либо.Если вы хотите узнать, произошло ли исключение, или получить фактический объект исключения, PyErr_Occurred()
это то, что вам следует позвонить.Он возвращает текущее исключение тип (а не фактический объект исключения) как заимствованную ссылку или NULL, если ничего не установлено.Если вы хотите просто напечатать обратную трассировку в stderr, PyErr_Print()
и PyErr_Clear()
это то, что вы хотите использовать.Для более детальной проверки фактической ошибки в вашем коде: PyErr_Fetch()
получает текущий объект исключения и связанную с ним обратную трассировку (он дает вам ту же информацию, что и sys.exc_info()
в коде Python.) Учитывая все обстоятельства, вам редко захочется так глубоко вникать в обработку исключений в коде C.