为什么命名元组总是由python的GC跟踪?

mob*_*ben 4 python garbage-collection

正如我们(或至少我)在这个答案中所学到的那样,只有包含不可变值的简单元组才会被python的垃圾收集器跟踪,一旦它发现它们永远不会参与引用循环:

>>> import gc
>>> x = (1, 2)
>>> gc.is_tracked(x)
True
>>> gc.collect()
0
>>> gc.is_tracked(x)
False
Run Code Online (Sandbox Code Playgroud)

为什么这不是namedtuple的情况,它是来自具有命名字段的collections模块的元组的子类?

>>> import gc
>>> from collections import namedtuple
>>> foo = namedtuple('foo', ['x', 'y'])
>>> x = foo(1, 2)
>>> gc.is_tracked(x)
True
>>> gc.collect()
0
>>> gc.is_tracked(x)
True
Run Code Online (Sandbox Code Playgroud)

他们的实现中是否存在一些可以防止这种情况或者只是被忽视的东西?

Bak*_*riu 7

我能找到的关于这一点的唯一评论是在gcmodule.cPython源文件中:

注意:关于可变对象的未跟踪.某些类型的容器不能参与引用循环,因此不需要被垃圾收集器跟踪.取消跟踪这些对象可以降低垃圾收集的成本.但是,确定哪些对象可能未被跟踪不是免费的,并且必须将成本与垃圾收集的益处进行权衡.

何时解开容器有两种可能的策略:

  1. 创建容器时.
  2. 当垃圾收集器检查容器时.

不需要跟踪仅包含不可变对象(整数,字符串等,以及递归,不可变对象的元组)的元组.解释器创建了大量元组,其中许多元素在垃圾收集之前无法生存.因此,在创建时取消符合条件的元组是不值得的.

而是在创建时跟踪除空元组之外的所有元组.在垃圾收集期间,确定是否可以未跟踪任何幸存的元组.如果元组的所有内容都未被跟踪,则元组可以不被跟踪.在所有垃圾收集周期中检查元组是否未跟踪.攻击元组可能需要不止一个周期.

也不需要跟踪仅包含不可变对象的字典.字典在创建时未跟踪.如果将跟踪的项目插入到字典中(作为键或值),则会跟踪字典.在完整的垃圾收集(所有代)中,收集器将跟踪其内容未被跟踪的任何字典.

该模块提供python函数is_tracked(obj),该函数返回对象的当前跟踪状态.后续的垃圾收集可能会更改对象的跟踪状态.在问题中引入了某些容器​​的未跟踪#4688,并且针对问题对算法进行了改进#14775.

(请参阅链接的问题以查看为了允许取消跟踪而引入的真实代码)

这个注释有点含糊不清,但是并没有说明选择哪个"未跟踪"对象的算法适用于通用容器.这意味着代码只检查tuples(和dicts),而不是它们的子类.

你可以在文件的代码中看到这个:

/* Try to untrack all currently tracked dictionaries */
static void
untrack_dicts(PyGC_Head *head)
{
    PyGC_Head *next, *gc = head->gc.gc_next;
    while (gc != head) {
        PyObject *op = FROM_GC(gc);
        next = gc->gc.gc_next;
        if (PyDict_CheckExact(op))
            _PyDict_MaybeUntrack(op);
        gc = next;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意PyDict_CheckExact,和:

static void
move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
{
    PyGC_Head *gc = young->gc.gc_next;

  /* omissis */
            if (PyTuple_CheckExact(op)) {
                _PyTuple_MaybeUntrack(op);
            }
Run Code Online (Sandbox Code Playgroud)

请注意PyTuple_CheckExact.

还要注意,子类tuple不必是不可变的.这意味着如果您想将此机制扩展到外部tuple并且dict您需要一个通用is_immutable函数.如果可能由于Python的动态性(例如,类的方法可能在运行时改变,而这是不可能的,因为它是内置类型),这将是非常昂贵的tuple.因此,开发者选择坚持一些特殊情况只有一些着名的内置插件.


这说,我相信它们也可以是特例,namedtuple因为它们非常简单.例如,当您调用namedtuple创建类时会出现一些问题,因此GC应检查子类.这可能是代码的问题,如:

class MyTuple(namedtuple('A', 'a b')):
    # whatever code you want
    pass
Run Code Online (Sandbox Code Playgroud)

因为MyTuple类只需是一成不变的,所以GC应该检查类是直接的子类namedtuple是安全的.但是我很确定这种情况有变通方法.

他们可能没有,因为namedtuples是标准库的一部分,而不是python核心.也许开发人员不想让核心依赖于标准库的模块.

那么,回答你的问题:

  • 不,他们的实施中没有任何内容可以防止namedtuples的未跟踪
  • 不,我相信他们并没有 "完全忽略"这一点.但是,只有python开发人员才能明确回答他们选择不包含它们的原因.我的猜测是,他们认为它不会为变化提供足够大的好处,他们不想让核心依赖于标准库.