在CPython中,我有两种类型的对象,它们彼此紧密相连。
#include <Python.h>
#include <structmember.h>
typedef struct pyt PYT;
struct pyt { PyObject_HEAD PYT *other; };
static void dealloc (PYT *self) {
Py_CLEAR(self->other);
printf("dealloc object at %pn", self);
PyObject_GC_Del(self);
}
static PyTypeObject Pyt2Type = {
PyObject_HEAD_INIT(NULL)
0, "pyt.Pyt2", sizeof(PYT), 0,
(destructor) dealloc
};
static PyObject * new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
PYT *self = PyObject_GC_New(PYT, type);
if (!self) return NULL;
self->other = PyObject_GC_New(PYT, &Pyt2Type);
if (!self->other) { Py_DECREF(self); return NULL; }
return Py_INCREF(self), self->other->other = self, (PyObject *) self;
}
static PyTypeObject Pyt1Type = {
PyObject_HEAD_INIT(NULL)
0, "pyt.Pyt1", sizeof(PYT), 0,
(destructor) dealloc
};
static int traverse (PYT *self, visitproc visit, void *arg) {
Py_VISIT(self->other);
return 0;
}
static int clear (PYT *self) {
Py_CLEAR(self->other);
return 0;
}
static PyMemberDef members[] = {
{"other", T_OBJECT, offsetof(PYT, other), RO, "other"},
{ NULL }
};
static PyMethodDef methods[] = {{ NULL }};
PyMODINIT_FUNC initpyt ( void ) {
PyObject* m;
Pyt1Type.tp_flags = Pyt2Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC;
Pyt1Type.tp_traverse = Pyt2Type.tp_traverse = (traverseproc) traverse;
Pyt1Type.tp_clear = Pyt2Type.tp_clear = (inquiry) clear;
Pyt1Type.tp_members = Pyt2Type.tp_members = members;
Pyt1Type.tp_new = new;
if (PyType_Ready(&Pyt1Type) < 0) return;
if (PyType_Ready(&Pyt2Type) < 0) return;
m = Py_InitModule("pyt", methods);
Py_INCREF(&Pyt1Type), PyModule_AddObject(m, "Pyt", (PyObject *) &Pyt1Type);
}
使用我的测试脚本
from distutils.core import Extension, setup
import sys, gc
sys.argv.extend(["build_ext", "-i"])
setup(ext_modules = [Extension('pyt', ['pyt.c'])])
from pyt import Pyt
pyt = Pyt()
print pyt, sys.getrefcount(pyt)
pyt = pyt.other
print pyt, sys.getrefcount(pyt)
del pyt
gc.collect()
我得到的输出如下
<pyt.Pyt1 object at 0x7fbc26540138> 3
<pyt.Pyt2 object at 0x7fbc26540150> 3
最后不会删除这些对象,因为每个对象都保留对另一个对象的引用,从而创建一个封闭的循环。在其他代码中,我使用一种方法,我只是保留对象,直到两者都有 refcount 为 0,我怀疑这是不好的做法。现在我在这里尝试使用垃圾收集器,但仍然没有收集对象。
这里出了什么问题?我错过了什么?
弱引用来执行此操作(请参阅weakref
模块)。 但通常最好只依靠垃圾回收器。 其他人可能会创建一个涉及您的对象的大型引用循环,然后您无论如何都会依赖 GC,因此您不妨将其用于简单情况。
请解释一下你所说的"严重失败"是什么意思。
关于(大多数)垃圾回收语言,需要注意的重要一点是,不能保证在对象变得无法访问时立即删除对象。一旦对象变得无法访问,它完全取决于垃圾回收器何时释放关联的资源,如果没有内存压力,则可能最晚在程序结束时。
如果您没有为链接类设置__del__
方法,则垃圾回收器应该可以正常工作。它不会立即清理您的对象,因为检测引用周期的函数比简单的引用计数成本更高,因此很少运行。
使用纯 python 类的示例
import gc
import weakref
class Obj(object): pass
x = Obj()
y = Obj()
x.y = y, y.x = x
ref = weakref.ref(x)
print(ref())
del x, y
print(ref())
gc.collect()
print(ref())
输出:
<__main__.Obj object at 0x7f81c8ccc7b8>
<__main__.Obj object at 0x7f81c8ccc7b8>
None
好的,我终于找到了我的问题。我没有开始跟踪PyObject_GC_Track
.
使用垃圾回收器时,Python 需要一些步骤:
- 向
tp_flags
添加Py_TPFLAGS_HAVE_GC
- 添加
tp_traverse
,如果需要,添加tp_clear
函数 - 使用
PyObject_GC_New
或类似函数创建对象 - 在完全初始化的对象上调用
PyObject_GC_Track
- 使用
PyObject_GC_Del
或类似功能删除对象
所以在这里修改new
函数就足够了。
static PyObject * new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
PYT *self = PyObject_GC_New(PYT, type);
if (!self) return NULL;
self->other = PyObject_GC_New(PYT, &Pyt2Type);
if (!self->other) { Py_DECREF(self); return NULL; }
self->other->other = (Py_INCREF(self), self);
PyObject_GC_Track((PyObject *) self);
PyObject_GC_Track((PyObject *) self->other);
return (PyObject *) self;
}
输出为
<pyt.Pyt1 object at 0x7f4904fe1398> 4
<pyt.Pyt2 object at 0x7f4904fe15c8> 4
dealloc object at 0x7f4904fe15c8
dealloc object at 0x7f4904fe1398