支持循环垃圾回收 — Python 文档

来自菜鸟教程
Python/docs/3.8/c-api/gcsupport
跳转至:导航、​搜索

支持循环垃圾回收

Python 对检测和收集涉及循环引用的垃圾的支持需要对象类型的支持,这些对象类型是其他对象(也可能是容器)的“容器”。 不存储对其他对象的引用,或只存储对原子类型(例如数字或字符串)的引用的类型,不需要为垃圾收集提供任何显式支持。

要创建容器类型,类型对象的 tp_flags 字段必须包含 Py_TPFLAGS_HAVE_GC 并提供 tp_traverse 处理程序的实现。 如果类型的实例是可变的,则还必须提供 tp_clear 实现。

Py_TPFLAGS_HAVE_GC
具有此标志集的类型的对象必须符合此处记录的规则。 为方便起见,这些对象将被称为容器对象。

容器类型的构造函数必须符合两条规则:

  1. 必须使用 PyObject_GC_New()PyObject_GC_NewVar() 为对象分配内存。
  2. 一旦所有可能包含对其他容器的引用的字段被初始化,它必须调用 PyObject_GC_Track()
TYPE *PyObject_GC_New(TYPE, PyTypeObject *type)
类似于 PyObject_New() 但对于设置了 Py_TPFLAGS_HAVE_GC 标志的容器对象。
TYPE *PyObject_GC_NewVar(TYPE, PyTypeObject *type, Py_ssize_t size)
类似于 PyObject_NewVar() 但对于设置了 Py_TPFLAGS_HAVE_GC 标志的容器对象。
TYPE *PyObject_GC_Resize(TYPE, PyVarObject *op, Py_ssize_t newsize)
调整由 PyObject_NewVar() 分配的对象的大小。 失败时返回调整大小的对象或 NULLop 还不能被收集器跟踪。
void PyObject_GC_Track(PyObject *op)
将对象 op 添加到收集器跟踪的容器对象集中。 收集器可以在意外的时间运行,因此对象在被跟踪时必须是有效的。 一旦 tp_traverse 处理程序后面的所有字段都有效,通常在构造函数的末尾附近,应该调用它。

同样,对象的解除分配器必须符合一对类似的规则:

  1. 在引用其他容器的字段无效之前,必须调用 PyObject_GC_UnTrack()
  2. 必须使用 PyObject_GC_Del() 释放对象的内存。
void PyObject_GC_Del(void *op)
使用 PyObject_GC_New()PyObject_GC_NewVar() 释放分配给对象的内存。
void PyObject_GC_UnTrack(void *op)
从收集器跟踪的容器对象集中移除对象 op。 请注意,可以在此对象上再次调用 PyObject_GC_Track() 以将其添加回跟踪对象集。 解除分配器(tp_dealloc 处理程序)应该在 tp_traverse 处理程序使用的任何字段变得无效之前为对象调用它。

3.8 版更改: _PyObject_GC_TRACK()_PyObject_GC_UNTRACK() 宏已从公共 C API 中删除。


tp_traverse 处理程序接受这种类型的函数参数:

typedef int (*visitproc)(PyObject *object, void *arg)
传递给 tp_traverse 处理程序的访问者函数的类型。 该函数应该使用一个对象作为 objecttp_traverse 处理程序的第三个参数作为 arg 来调用。 Python 核心使用多个访问者函数来实现循环垃圾检测; 预计用户不需要编写自己的访问者函数。

tp_traverse 处理程序必须具有以下类型:

typedef int (*traverseproc)(PyObject *self, visitproc visit, void *arg)
容器对象的遍历函数。 实现必须为 self 直接包含的每个对象调用 visit 函数,visit 的参数是包含的对象,arg传递给处理程序的值。 不得使用 NULL 对象参数调用 visit 函数。 如果 visit 返回非零值,则应立即返回该值。

为了简化编写 tp_traverse 处理程序,提供了一个 Py_VISIT() 宏。 为了使用这个宏,tp_traverse 实现必须准确地命名它的参数 visitarg

void Py_VISIT(PyObject *o)

如果 o 不是 NULL,则调用 visit 回调,参数为 oarg。 如果 visit 返回一个非零值,则返回它。 使用这个宏, tp_traverse 处理程序看起来像:

static int
my_traverse(Noddy *self, visitproc visit, void *arg)
{
    Py_VISIT(self->foo);
    Py_VISIT(self->bar);
    return 0;
}

tp_clear 处理程序必须是 inquiry 类型,或者 NULL 如果对象是不可变的。

typedef int (*inquiry)(PyObject *self)
删除可能已创建引用循环的引用。 不可变对象不必定义此方法,因为它们永远不能直接创建引用循环。 请注意,调用此方法后对象必须仍然有效(不要只是在引用上调用 Py_DECREF())。 如果收集器检测到此对象涉及引用循环,则它会调用此方法。