我应该对这个块中的任何 PyObjects 使用 Py_INCREF 吗?我也正确 Py_DECREFing 我的对象吗?

Posted

技术标签:

【中文标题】我应该对这个块中的任何 PyObjects 使用 Py_INCREF 吗?我也正确 Py_DECREFing 我的对象吗?【英文标题】:Should I use Py_INCREF for any of my PyObjects in this block? Also am I Py_DECREFing my objects correctly? 【发布时间】:2018-04-26 21:37:04 【问题描述】:

每当我调用这个函数时,每次调用的内存使用量都会增加很多,所以我认为这里有一些内存泄漏。

PyObject *pScript, *pModule, *pFunc, *pValue;
PyObject *pArgs = NULL;
long ret = 1;

// Initialize python, set system path and load the module
pScript = SetPyObjectString(PYTHON_SCRIPT_NAME);
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('"PYTHON_SCRIPT_PATH"')");
pModule = PyImport_Import(pScript);
Py_XDECREF(pScript);

if (pModule != NULL) 
    // Get function object from python module
    pFunc = PyObject_GetAttrString(pModule, operation.c_str());

    if (pFunc && PyCallable_Check(pFunc)) 
        // Create argument(s) as Python tuples
        if (operation == UPDATE_KEY) 
            // If operation is Update key, create two arguments - key and value
            pArgs = PyTuple_New(2);
        
        else 
            pArgs = PyTuple_New(1);
        

        pValue = SetPyObjectString(key.c_str());
        // Set argument(s) with key/value strings
        PyTuple_SetItem(pArgs, 0, pValue);
        if (operation == UPDATE_KEY) 
            // If operation is Update key, set two arguments - key and value
            pValue = SetPyObjectString(value.c_str());
            PyTuple_SetItem(pArgs, 1, pValue);
        

        // Call the function using function object and arguments
        pValue = PyObject_CallObject(pFunc, pArgs);
        Py_XDECREF(pArgs);

        if (pValue != NULL) 
            // Parse the return values
            ret = PyLong_AsLong(PyList_GetItem(pValue, 0));
            value = GetPyObjectString(PyList_GetItem(pValue, 1));
        
        else 
            ERROR("Function call to %s failed", operation.c_str());
        

        Py_XDECREF(pValue);
        Py_XDECREF(pFunc);
    
    else 
        ERROR("Cannot find function in python module");
    
    Py_XDECREF(pModule);

else 
    ERROR("Failed to load python module");

当我的代码中的这个 C++ sn-p 调用 python 脚本时,我泄漏了一些内存,我想知道为什么。我认为我的 Py_DECREF 做错了什么。任何帮助将不胜感激。

【问题讨论】:

您至少缺少一个 decref — 如果 pFunc 不为空,但它不可调用怎么办?并且您将新值重新分配给 pValue 而不释放旧值。您可能会遗漏更多信息,但您确实应该使用内存调试器,而不是试图通过检查来猜测。或者,甚至更好——如果您使用的是 C++,而不是 C,为什么不使用 PyCXX 或 boost::python 或其他使用 RAII 为您处理引用计数的 C++ 包装器? @abarnert 我不能使用任何其他包装器,因为这是一个有很多依赖项的大项目的一小部分。除了默认的 Python C API 之外,很难使用其他任何东西。你能看看 PyObjects pArgs 和 pValue 吗?我不明白哪个函数需要 DECREF,哪个函数需要 INCREF 您可以在大型项目中本地使用 PyCXX,您可能无法使用一些更高级别的功能,例如扩展构建器工具或 PythonSTL 转换,但您可以轻松使用智能指针。或者,如果没有,您可以编写自己的智能指针。但实际上,如果您无法理解什么需要 DECREF,那么您真的不应该编写此代码。通过复制和粘贴您不理解的代码并对其进行霰弹枪调试直到它似乎可以工作,您无法做到正确的引用计数。 好的,我会研究 PyCXX 或 boost::python。谢谢! @VenkatKrishnan 如果您正在寻找包装,我推荐pybind11。 【参考方案1】:

我一眼就发现了一个缺失的 deref:


pFunc = PyObject_GetAttrString(pModule, operation.c_str());

if (pFunc && PyCallable_Check(pFunc)) 
    // ...
    Py_XDECREF(pFunc);

这将泄漏任何匹配operation 的不可调用属性。


pValue 的两次重新分配……我认为这没关系,因为PyTuple_SetItem 窃取了对每个原始值的引用。


对于您询问的这一行:

value = GetPyObjectString(PyList_GetItem(pValue, 1));

PyList_GetItem 返回一个借用的引用,所以你不去声明它是正确的。

但是我在任何地方都没有看到valueGetPyObjectString 的声明,所以我不知道那部分在做什么。也许它只是从 PyUnicodeObject * 中获取一个借来的缓冲区并将其复制到一些 C++ wstring 或 UTF-32 字符串类型中,或者它可能正在泄漏 Python 对象或复制的缓冲区,或者返回一个你刚刚得到的原始 C 缓冲区在这里泄漏,或者……谁知道?


但我当然不相信互联网上的某个人通过快速扫描找到了所有这些。学习使用内存调试器。

或者:您正在使用 C++。 RAII 几乎是使用 C++ 的全部意义——换句话说,您可以使用智能指针自动为您定义事物,而不是使用原始的 PyObject * 值。或者,更好的是,使用像 PyCXX 这样的现成库。

【讨论】:

好的,谢谢。这就是我一直在寻找的。因为这个docs.python.org/3/extending/extending.html#ownership-rules,我很困惑。它说 PyTuple_SetItem() 接管所有权。我现在将为此使用两个不同的 PyObjects。另外,ret = PyLong_AsLong(PyList_GetItem(pValue, 0)); 在这一行中,我应该 DECREF 什么吗? @VenkatKrishnan 哎呀,我可能错过了。如果我弄错了,我会检查并更新答案。我已经很久没有为任何重要的东西编写原始 C API 扩展了——如果我不能使用 Cython、PyCXX 或 RustPy 等,我通常只创建一个普通的 C API 并使用 cffi 在 Python 中编写绑定或 ctypes。部分原因是这些东西调试起来很痛苦。但是你甚至都没有尝试调试它,只是试图静态地让它正确,这让它变得更加痛苦。

以上是关于我应该对这个块中的任何 PyObjects 使用 Py_INCREF 吗?我也正确 Py_DECREFing 我的对象吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 PyObjects 声明 Boost.Python C++ 类

如何从 geolite2 块中了解网络长度

UIView 动画块中的图层动画

return 语句应该是代码块中的最后一条指令吗?

如何让 GHC 对 do 块中的不完整模式绑定发出警告?

使用 Web API 检索块中的文件数据以在浏览器中显示 (WIP)