Cython : return一个<object>PyObject*,它会泄漏吗?

Cython : return a <object>PyObject*, will it leak?

我目前正在使用 Cython 包装一个库。为此,我想重新使用纯 C 绑定的一个函数。

这是基本设置:

在 mylib.pxd 我做 :

cdef extern from old_lib.h:
    PyObject* get_pyobject()

然后将 old_lib.c 作为源文件传递到我的扩展中:

setup(ext_modules=[Extension("mylib", sources=["mylib.pxd", "old_lib.c"])])

在 mylib.pxd 中,我使用 get_pyobject 创建一个我想要 return 的新对象,如下所示:

cdef PyObject* ptr
ptr = get_pyobject()
return <object>ptr

这给了我想要的行为,但我担心这会泄漏 ptr 引用。

会吗?我很困惑,因为我发现(旧的)引用说你应该自己管理 PyObject* 引用并相应地调用 Py_INCREF/DECREF 但似乎在 Cython FAQ 中他们说:

Note that the lifetime of the object is only bound to its owned references, not to any C pointers that happen to point to it.

这是否意味着每当 returned 值被丢弃时,ptr 将被垃圾收集?

old_lib.c 中,流程是这样的:

PyObject* get_pyobject()
{
     PyTypedObject* typeptr = PyObject_NEW_VAR(MyType, &Type, size)
     fill_attribute(typeptr->attrib)
     return (PyObject*)typeptr
}

其中 PyObject_NEW_VAR 在 python 标准库中实现(objimpl.h:196 在我的版本中)使用 PyObject_InitVar。因此,returned 引用是 borrowed 引用,但由于使用了 PyObject_MALLOC,我猜这是对该对象的唯一引用。相关代码:

#define PyObject_NEW_VAR(type, typeobj, n) \
( (type *) PyObject_InitVar( \
       (PyVarObject *) PyObject_MALLOC(_PyObject_VAR_SIZE((typeobj),(n)) ),\
       (typeobj), (n)) )

编辑: 我已经检查过,当使用上面的代码时,sys.getrefcount returns 3。据我所知,当我创建对象时,它的引用计数为 1。然后,在转换它时到 object 它的引用计数被撞到 2。因此它永远不会被垃圾收集(除非有一种方法可以删除对只有一个可访问指针的对象的两个引用计数)和泄漏。 如果我插入一个 PY_DECREF,它仍然可以正常工作 returns 2。我还花时间直接在 Cython 中重写了那个函数,它 returns 2.

查看 old documentation PyObject_NEW_VAR 是函数 PyObject_NewVar 的宏版本,它(正如@MadPhysicist 所说) returns a "new reference" (即有重新计票 1)。我怀疑不再鼓励您使用该宏,因此它已从最近的文档中消失。

事实上,它是根据 returns a "borrowed reference" 的东西实现的,这可能只是一个实现细节,而不是 returns a "borrowed reference"本身。

关于 Cython 的行为,转换为 <object> 会增加引用计数,因此会导致内存泄漏。我建议的诊断方法是查看引用计数,如下所示:

from cpython.ref cimport PyObject # somewhere at the top

def whatever_function():
    cdef PyObject* ptr
    ptr = get_pyobject()
    print ptr.ob_refcnt # prints 1
    ret_val = <object>ptr
    print ptr.ob_refcnt # prints 2,
        # but it will only every be decremented to 1, so never be freed
    return ret_val

就修复它而言,您有两种选择 - 您可以自己减少一次引用计数,或者您可以更改函数的 Cython 包装

cdef extern from old_lib.h:
   object get_pyobject()

(不用担心它与头文件不完全匹配!)。 Cython 将其解释为“get_pyobject() returns 一个新的引用,所以我们不会自己增加它,而是从这里自动处理引用计数。”