支持循环垃圾收集

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

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

  • Py_TPFLAGS_HAVE_GC

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

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

  • 必须使用PyObject_GC_New()PyObject_GC_NewVar()分配对象的内存。

  • 初始化所有可能包含对其他容器的引用的字段后,它必须调用PyObject_GC_Track()

  • TYPE * PyObject_GC_New(TYPE,PyTypeObject ** type *)

  • TYPE * PyObject_GC_NewVar(TYPE,PyTypeObject ** type *,Py_ssize_t * size *)

  • TYPE * PyObject_GC_Resize(TYPE,PyVarObject ** op *,Py_ssize_t * newsize *)

    • 调整由PyObject_NewVar()分配的对象的大小。返回调整大小的对象或失败时返回NULL。 * op *尚未被收集器跟踪。
  • 无效PyObject_GC_Track(PyObject ** op *)

    • 将对象* op *添加到收集器跟踪的一组容器对象中。收集器可能在意外的时间运行,因此在跟踪对象时必须有效。一旦tp_traverse处理程序后面的所有字段都有效(通常在构造函数的结尾附近),则应调用此方法。

同样,对象的解除分配器也必须遵循Pair相似的规则:

  • 在引用其他容器的字段无效之前,必须调用PyObject_GC_UnTrack()

  • 必须使用PyObject_GC_Del()释放对象的内存。

  • 无效PyObject_GC_Del(void ** op *)

  • 无效PyObject_GC_UnTrack(void ** op *)

    • 从收集器跟踪的容器对象集中删除对象* op *。请注意,可以再次在此对象上调用PyObject_GC_Track(),以将其重新添加到跟踪的对象集中。在tp_traverse处理程序使用的任何字段变为无效之前,应为对象调用 deallocator(tp_dealloc处理程序)。

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

tp_traverse处理程序接受以下类型的函数参数:

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

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

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

为了简化编写tp_traverse处理程序的过程,提供了一个Py_VISIT()宏。为了使用此宏,tp_traverse实现必须将其参数精确命名为* visit arg *:

  • 无效Py_VISIT(PyObject ** o *)
    • 如果* o 不是NULL,请使用参数 o arg 调用 visit 回调。如果 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

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