PyEval_CallObject 偶尔会在循环中失败
我在使用 Python C API 时遇到了一些困难。我正在调用 python 方法以大约 60hz 执行一些游戏 AI。它在大多数情况下都有效,但每隔一秒左右,对 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
{
我还没能找到如何提取异常,或者...没有测试每个异常
}
}
我是否已经接近正确执行此操作?就像我说的,它大部分有效,但我真的很想了解为什么我会收到错误。
预先感谢您的帮助。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您可以根据需要多次调用
PyImport_Import()
,但您只会不断获取相同的模块对象。 Python 缓存导入。此外,您应该只使用PyImport_ImportModule()
,而不是创建新的 Python 字符串并泄漏引用(从而泄漏对象),它采用 const char *。PyImport_Import*()
返回一个新引用,但完成后您应该对其调用Py_DECREF()
。将模块存储在全局中应该不是问题,只要您拥有对它的引用(您在此处所做的)。在对
PyEval_CallObject()
的调用中,您不会检查结果的Py_BuildValue()
错误,并且当您完成它时也没有调用Py_DECREF()
,因此您也泄漏了该对象。为了将 Python float 转换为 C double,您可能应该只调用
PyFloat_AsDouble()
而不是使用PyArg_Parse()
(并记住要测试异常)到实际的错误处理:
PyErr_ExceptionMatches()
仅当您实际想要测试异常是否与某些内容匹配时才有用。如果您想知道是否发生了异常,或者获取实际的异常对象,您应该调用PyErr_Occurred()
。它返回当前异常类型(不是实际的异常对象)作为借用引用,如果未设置,则返回 NULL。如果您只想打印 stderr 的回溯,则需要使用PyErr_Print()
和PyErr_Clear()
。为了对代码中的实际错误进行更细粒度的检查,PyErr_Fetch()
可以获取当前异常对象以及与其关联的回溯(它可以获取与 sys.exc_info 相同的信息) () 在 Python 代码中。)考虑到所有因素,您很少想深入了解 C 代码中的异常处理。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 usePyImport_ImportModule()
, which takes aconst char *
.PyImport_Import*()
return a new reference, though, you should callPy_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 ofPy_BuildValue()
for errors, and you're also not callingPy_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 withPyArg_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()
andPyErr_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 assys.exc_info()
in Python code.) All things considered you rarely want to get that deeply into the exception handling in C code.