我能找到的唯一评论是在gcmodule.c
Python 源文件中:
注意:关于不跟踪可变对象。某些类型的容器不能参与引用循环,因此不需要被垃圾收集器跟踪。取消跟踪这些对象可以降低垃圾收集的成本。但是,确定哪些对象可能未被跟踪并不是免费的,必须权衡成本与垃圾收集的好处。
何时取消跟踪容器有两种可能的策略:
- 创建容器时。
- 当垃圾收集器检查容器时。
不需要跟踪仅包含不可变对象(整数、字符串等,以及递归地包含不可变对象的元组)的元组。解释器创建了大量的元组,其中许多在垃圾收集之前将无法生存。因此,在创建时取消跟踪符合条件的元组是不值得的。
相反,除了空元组之外的所有元组在创建时都会被跟踪。在垃圾收集期间,确定是否可以不跟踪任何幸存的元组。如果一个元组的所有内容都没有被跟踪,那么它就可以被取消跟踪。在所有垃圾收集周期中检查元组是否取消跟踪。取消跟踪一个元组可能需要一个以上的周期。
只包含不可变对象的字典也不需要被跟踪。创建时不跟踪字典。如果将跟踪项插入字典(作为键或值),则字典将被跟踪。在完整的垃圾收集(所有代)期间,收集器将取消跟踪任何内容未被跟踪的字典。
该模块提供了python函数is_tracked(obj)
,返回对象当前的跟踪状态。后续的垃圾回收可能会改变对象的跟踪状态。在 issue 中引入了某些容器的 Untracking #4688
,并针对 issue 改进了算法#14775
。
(请参阅链接问题以查看引入以允许取消跟踪的真实代码)
这个评论有点模棱两可,但是它没有说明选择“取消跟踪”哪个对象的算法适用于通用容器。这意味着代码只检查tuple
s ( 和dict
s),而不检查它们的子类。
您可以在文件的代码中看到这一点:
/* 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;
}
}
注意调用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);
}
请注意调用PyTuple_CheckExact
.
另请注意,子类tuple
不必是不可变的。这意味着如果您想将此机制扩展到外部tuple
并且dict
您需要一个通用is_immutable
函数。这将是非常昂贵的,如果可能的话,由于 Python 的动态性(例如,类的方法可能会在运行时改变,而这是不可能的,tuple
因为它是一个内置类型)。因此,开发人员选择坚持少数特殊情况,只使用一些知名的内置插件。
这就是说,我相信它们也可以是特殊情况namedtuple
,因为它们是非常简单的类。会有一些问题,例如当您调用namedtuple
您正在创建一个新类时,因此 GC 应该检查子类。这可能是以下代码的问题:
class MyTuple(namedtuple('A', 'a b')):
# whatever code you want
pass
因为MyTuple
类不需要是不可变的,所以 GC 应该检查该类是安全的直接子类namedtuple
。但是我很确定这种情况有解决方法。
他们可能没有,因为namedtuple
s 是标准库的一部分,而不是 python 核心。也许开发人员不想让核心依赖于标准库的模块。
所以,回答你的问题:
- 不,它们的实现中没有任何东西可以固有地阻止对
namedtuple
s 的取消跟踪
- 不,我相信他们并没有“简单地忽视”这一点。然而,只有 python 开发人员才能明确回答他们为什么选择不包含它们。我的猜测是,他们认为这不会为更改提供足够大的好处,并且他们不想让核心依赖于标准库。