PyEval_CallObject 偶尔会在循环中失败

发布于 2024-08-21 01:29:08 字数 1160 浏览 5 评论 0原文

我在使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

雪落纷纷 2024-08-28 01:29:08

您可以根据需要多次调用 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 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文