使对象类型支持循环垃圾回收

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()。

同样的,对象的释放器必须符合两个类似的规则:

  1. 在引用其它容器的字段失效前,必须调用 PyObject_GC_UnTrack()。

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

    警告

    如果某个类型添加了 Py_TPFLAGS_HAVE_GC,那么它*必须*实现至少一个 tp_traverse 处理程序,或显式使用基类中提供的处理程序。

    当调用 PyType_Ready() 或者某些间接调用该函数的 API 如 PyType_FromSpecWithBases() 或 PyType_FromSpec() 时解释器将自动填充 tp_flags, tp_traverse 和 tp_clear 字段,如果该类型是继承自实现了垃圾回收器协议的类并且该子类 没有 包括 Py_TPFLAGS_HAVE_GC 旗标的话。

PyObject_GC_New(TYPE, typeobj)

类似于 PyObject_New 但专用于设置了 Py_TPFLAGS_HAVE_GC 旗标的容器对象。

请不要直接调用此函数为对象分配内存;而应调用类型的 tp_alloc 槽位。

在填充类型的 tp_alloc 槽位时,推荐使用 PyType_GenericAlloc() 而不是使用简单调用此宏的自定义函数。

由此宏分配的内存必须用 PyObject_GC_Del() 来释放(通常是通过对象的 tp_free 槽位来调用)。

PyObject_GC_NewVar(TYPE, typeobj, size)

与 PyObject_NewVar 类似但专用于设置了 Py_TPFLAGS_HAVE_GC 旗标的容器对象。

请不要直接调用此函数为对象分配内存;而应调用类型的 tp_alloc 槽位。

在填充类型的 tp_alloc 槽位时,推荐使用 PyType_GenericAlloc() 而不是使用简单调用此宏的自定义函数。

由此宏分配的内存必须用 PyObject_GC_Del() 来释放(通常是通过对象的 tp_free 槽位来调用)。

PyObject *PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *type, size_t extra_size)
这是 不稳定 API。它可能在次要版本中不经警告地被更改。

与 PyObject_GC_New 类似但会在对象的末尾分配 extra_size 个字节(在 tp_basicsize 偏移量处)。除 Python 对象标头 外,分配的内存将初始化为零。

附加数据将与对象一起被释放,但在其他情况下则不会由 Python 来管理。

由此函数分配的内存必须用 PyObject_GC_Del() 来释放(通常是通过对象的 tp_free 槽位来调用)。

警告

此函数被标记为非稳定的因为在实例之后保留附加数据的机制尚未确定。要分配可变数量的字段,推荐改用 PyVarObject 和 tp_itemsize 字段。

在 3.12 版本加入.

PyObject_GC_Resize(TYPE, op, newsize)

重新调整 PyObject_GC_NewVar 所分配对象的大小。 返回类型为 TYPE* (指向任意 C 类型)的调整后对象或在失败时返回 NULL。

op 必须为 PyVarObject* 类型并且不能已被回收器所追踪。 newsize 必须为 Py_ssize_t 类型。

void PyObject_GC_Track(PyObject *op)
属于 稳定 ABI.

把对象 op 加入到垃圾回收器跟踪的容器对象中。对象在被回收器跟踪时必须保持有效的,因为回收器可能在任何时候开始运行。在 tp_traverse 处理前的所有字段变为有效后,必须调用此函数,通常在靠近构造函数末尾的位置。

int PyObject_IS_GC(PyObject *obj)

如果对象实现了垃圾回收器协议则返回非零值,否则返回 0。

如果此函数返回 0 则对象无法被垃圾回收器追踪。

int PyObject_GC_IsTracked(PyObject *op)
属于 稳定 ABI 自 3.9 版起.

如果 op 对象的类型实现了 GC 协议且 op 目前正被垃圾回收器追踪则返回 1,否则返回 0。

这类似于 Python 函数 gc.is_tracked()。

在 3.9 版本加入.

int PyObject_GC_IsFinalized(PyObject *op)
属于 稳定 ABI 自 3.9 版起.

如果 op 对象的类型实现了 GC 协议且 op 已经被垃圾回收器终结则返回 1,否则返回 0。

这类似于 Python 函数 gc.is_finalized()。

在 3.9 版本加入.

void PyObject_GC_Del(void *op)
属于 稳定 ABI.

释放之前使用 PyObject_GC_New 或 PyObject_GC_NewVar 分配给对象的内存。

请不要直接调用此函数来释放对象的内存;而应调用类型的 tp_free 槽位。

请不要为由 PyObject_New, PyObject_NewVar 或相关联的分配函数分配的内存使用此宏;而应改用 PyObject_Free()。

void PyObject_GC_UnTrack(void *op)
属于 稳定 ABI.

从回收器跟踪的容器对象集合中移除 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)
属于 稳定 ABI.

传给 tp_traverse 处理的访问函数的类型。object 是容器中需要被遍历的一个对象,第三个形参对应于 tp_traverse 处理的 arg 。Python 核心使用多个访问者函数实现循环引用的垃圾检测,不需要用户自行实现访问者函数。

tp_clear 处理程序必须为 inquiry 类型,如果对象不可变则为 NULL 值。

typedef int (*inquiry)(PyObject *self)
属于 稳定 ABI.

丢弃产生循环引用的引用。不可变对象不需要声明此方法,因为它们不可能直接产生循环引用。需要注意的是,对象在调用此方法后必须仍是有效的(不能对引用只调用 Py_DECREF() 方法)。当垃圾回收器检测到该对象在循环引用中时,此方法会被调用。

遍历

tp_traverse 处理必须是以下类型:

typedef int (*traverseproc)(PyObject *self, visitproc visit, void *arg)
属于 稳定 ABI.

用于垃圾回收对象的 Traversal 函数,由垃圾回收器用于检测引用循环。实现时必须为 self 直接包含的每个对象调用 visit 函数;visit 的参数为被包含对象以及传递给处理器的 arg 值。visit 函数不得以 NULL 对象作为参数被调用。如果 visit 返回非零值,则应立即返回该值;否则,返回零。

一个典型的 tp_traverse 函数会在实例所拥有的每个属于 Python 对象的实例成员上调用 Py_VISIT() 便捷宏。 例如,下面是一个针对 threading.local 类的(略微过时的)遍历函数:

static int
local_traverse(PyObject *op, visitproc visit, void *arg)
{
    localobject *self = (localobject *) op;
    Py_VISIT(Py_TYPE(self));
    Py_VISIT(self->args);
    Py_VISIT(self->kw);
    Py_VISIT(self->dict);
    return 0;
}

备注

Py_VISIT() 要求传给 local_traverse() 的 visit 和 arg 形参具有这些指定名称;请不要随意命名它们。

堆分配类型 的实例会持有一个对其类型的引用。 因此它们的遍历函数必须访问其类型:

Py_VISIT(Py_TYPE(self));

或者,该类型可以通过调用堆分配超类中的 tp_traverse (若适用,也可调用其他堆分配类型中的相应方法)来委托这一职责。如果未这样做,则该类型对象可能不会被垃圾回收。

如果在 tp_flags 字段中设置了 Py_TPFLAGS_MANAGED_DICT 比特位,则遍历函数必须这样调用 PyObject_VisitManagedDict():

int err = PyObject_VisitManagedDict((PyObject*)self, visit, arg);
if (err) {
    return err;
}

只有那些由实例*拥有*的成员(即通过 strong references 将其关联到实例中的成员)才必须被访问。例如,如果一个对象通过 tp_weaklist 槽位支持弱引用,那么用于支持链表(即*tp_weaklist*所指向的内容)的指针**不得**被访问,因为该实例并不直接拥有指向自身的弱引用。

遍历函数存在一项限制:

警告

遍历函数不得产生任何副作用。实现代码不得修改任何 Python 对象的引用计数,也不得直接或间接地创建或销毁任何 Python 对象。

这意味着*大多数* Python C API 函数可能无法使用,因为它们可能会抛出新的异常、返回指向结果对象的新引用,或包含使用副作用的内部逻辑。此外,除非另有说明,那些恰好没有副作用的函数也可能在后续版本中突然开始产生副作用,且不会提前发出警告。

有关安全函数的列表,请参阅下方的 separate section。

如果在遍历过程中发生错误,请忽略该错误并返回 0。如果 tp_traverse 未能访问某些强引用,则涉及被跳过引用的垃圾引用循环将无法被可靠地打破,但正确性不会受到影响。例如,如果遍历需要获取一个无法及时获得的锁,则跳过该遍历;垃圾回收器将在后续遍历中再次访问您的对象。

备注

对于那些经证实无法参与引用循环的成员,可以跳过 Py_VISIT() 调用。在上述 local_traverse 示例中,还存在一个 self->key 成员,但它只能是 NULL 或 Python 字符串,因此无法成为引用循环的一部分。

在另一方面,即使你知道某个成员永远不会成为循环引用的一部分,作为调试的辅助你仍然可能想要访问它因此 gc 模块的 get_referents() 函数将会包括它。

备注

tp_traverse 函数可以从任何线程调用。

垃圾回收是一种“停止整个系统运行”的操作:即使在 自由线程 构建版本中,当 tp_traverse 处理程序运行时,也仅存在一个 attached 线程状态。

在 3.9 版本发生变更: 堆分配类型应当访问 tp_traverse 中的 Py_TYPE(self)。在较早的 Python 版本中,由于 bug 40217,这样做可能会导致在子类中发生崩溃。

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

Py_VISIT(o)

如果 PyObject* o 不是 NULL,则调用 visit 回调函数,传入参数 o 和 arg。如果 visit 返回非零值,则将该值返回。

这大致相当于:

#define Py_VISIT(o)                             \
   if (o) {                                    \
      int visit_result = visit(o, arg);         \
      if (visit_result != 0) {                  \
         return visit_result;                   \
      }                                         \
   }

可安全遍历的函数

以下函数和宏可在 tp_traverse 处理程序中安全使用:

“DuringGC”函数

以下函数*仅*应在 tp_traverse 处理程序中使用;在其他上下文中调用这些函数可能会引发意外后果。

这些函数与不带 _DuringGC 后缀的对应函数功能相同,但可以保证不会产生副作用;在失败时不会触发异常,并且会返回/设置 borrowed references,具体细节详见相应文档。

请注意,这些函数可能会失败(返回 NULL 或 -1),但由于它们不会设置异常,因此无法获取错误信息。在某些情况下,失败结果与成功的 NULL 结果无法区分。

void *PyObject_GetTypeData_DuringGC(PyObject *o, PyTypeObject *cls)
void *PyObject_GetItemData_DuringGC(PyObject *o)
void *PyType_GetModuleState_DuringGC(PyTypeObject *type)
void *PyModule_GetState_DuringGC(PyObject *module)
int PyModule_GetToken_DuringGC(PyObject *module, void **result)
属于 稳定 ABI 自 3.15 版起.

有关常用信息,请参阅 “DuringGC”函数。

在 3.15 版本加入.

int PyType_GetBaseByToken_DuringGC(PyTypeObject *type, void *tp_token, PyTypeObject **result)
属于 稳定 ABI 自 3.15 版起.

有关常用信息,请参阅 “DuringGC”函数。

将 *result 设置为 借入引用,而非强引用。该引用在 tp_traverse 处理程序调用期间有效。

在 3.15 版本加入.

PyObject *PyType_GetModule_DuringGC(PyTypeObject *type)
PyObject *PyType_GetModuleByToken_DuringGC(PyTypeObject *type, const void *mod_token)
返回值:借入的引用。 属于 稳定 ABI 自 3.15 版起.

有关常用信息,请参阅 “DuringGC”函数。

这些函数将返回 borrowed reference,它会在 tp_traverse 处理器调用持续期间保持可用。

在 3.15 版本加入.

控制垃圾回收器状态

这个 C-API 提供了以下函数用于控制垃圾回收的运行。

Py_ssize_t PyGC_Collect(void)
属于 稳定 ABI.

执行完全的垃圾回收,如果垃圾回收器已启用的话。 (请注意 gc.collect() 会无条件地执行它。)

返回已回收的 + 无法回收的不可获取对象的数量。如果垃圾回收器被禁用或已在执行回收,则立即返回 0。在垃圾回收期间发生的错误会被传给 sys.unraisablehook。此函数不会引发异常。

int PyGC_Enable(void)
属于 稳定 ABI 自 3.10 版起.

启用垃圾回收器:类似于 gc.enable()。返回之前的状态,0 为禁用而 1 为启用。

在 3.10 版本加入.

int PyGC_Disable(void)
属于 稳定 ABI 自 3.10 版起.

禁用垃圾回收器:类似于 gc.disable()。返回之前的状态,0 为禁用而 1 为启用。

在 3.10 版本加入.

int PyGC_IsEnabled(void)
属于 稳定 ABI 自 3.10 版起.

查询垃圾回收器的状态:类似于 gc.isenabled()。返回当前的状态,0 为禁用而 1 为启用。

在 3.10 版本加入.

查询垃圾回收器状态

该 C-API 提供了以下接口用于查询有关垃圾回收器的信息。

void PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg)
这是 不稳定 API。它可能在次要版本中不经警告地被更改。

在全部活动的支持 GC 的对象上运行所提供的 callback。 arg 会被传递给所有 callback 的调用。

警告

如果新对象被回调所(取消)分配,则它们是否会被访问是未定义的。

垃圾回收在运行期间被禁用。在回调中显式地运行回收可能导致未定义的行为,例如多次访问同一对象或完全不访问。

在 3.12 版本加入.

typedef int (*gcvisitobjects_t)(PyObject *object, void *arg)

要传给 PyUnstable_GC_VisitObjects() 的访问者函数的类型。 arg 与传给 PyUnstable_GC_VisitObjects 的 arg 相同。返回 1 以继续迭代,返回 0 以停止迭代。 其他返回值目前被保留因此返回任何其他值的行为都是未定义的。

在 3.12 版本加入.