正如我们(或至少我)在 this answer 中学到的那样,python 的垃圾收集器不会跟踪仅包含不可变值的简单元组,一旦发现它们永远不会参与引用循环:
>>> import gc
>>> x = (1, 2)
>>> gc.is_tracked(x)
True
>>> gc.collect()
0
>>> gc.is_tracked(x)
False
为什么 namedtuple 不是这种情况,它是 collections 模块中具有命名字段的 tuple 的子类?
>>> 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
他们的实现中是否有一些固有的东西可以防止这种情况,或者只是被忽视了?
最佳答案
我能找到的关于此的唯一评论是在 Python 源代码的 gcmodule.c
文件中:
(请参阅链接的问题以查看为允许取消跟踪而引入的真实代码)
这个评论有点含糊,但它没有说明选择“取消跟踪”哪个对象的算法适用于通用容器。这意味着代码只检查 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
是标准库的一部分,而不是 python 核心。也许开发人员不想让核心依赖于标准库的模块。所以,回答你的问题:
namedtuple
s 关于python - 为什么命名元组总是被python的GC跟踪?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/19770515/