正如我们(或至少我)在this answer中学习的那些仅包含不可变值的元组不会被python的垃圾收集器跟踪,一旦它发现它们永远不会参与引用循环:
>>> import gc
>>> x = (1, 2)
>>> gc.is_tracked(x)
True
>>> gc.collect()
0
>>> gc.is_tracked(x)
False
为什么namedtuple s不是这种情况,它是来自具有命名字段的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
他们的实施中是否有固有的东西可以阻止这种情况,还是只是被忽视了?
答案 0 :(得分:7)
我能找到的关于此的唯一评论是在Python源代码的gcmodule.c
文件中:
注意:有关未跟踪可变对象的信息。 某些类型的容器不能参与引用循环,因此不需要被垃圾收集器跟踪。 取消跟踪这些对象可以降低垃圾收集的成本。 但是,确定哪些对象可能未被跟踪不是免费的, 并且必须权衡成本与垃圾的利益 集合。
何时解开容器有两种可能的策略:
- 创建容器时。
- 当垃圾收集器检查容器时。
醇>仅包含不可变对象的元组(整数,字符串等, 并且递归地,不可变对象的元组)不需要 跟踪。解释器创建了大量的元组,其中许多都是 直到垃圾收集才能生存。因此 不值得在创建时取消符合条件的元组。
相反,除了空元组之外的所有元组都被跟踪 创建。在垃圾收集期间,确定是否有 幸存的元组可以不被攻击。如果全部,元组可以不被跟踪 其内容已被跟踪。检查元组 在所有垃圾收集周期中未跟踪。它可能需要更多 一个周期来取消一个元组。
仅包含不可变对象的字典也不需要 跟踪。字典在创建时未跟踪。如果跟踪 item被插入到字典中(作为键或值), 字典被跟踪。在完整的垃圾收集期间(全部 几代人,收藏家将不会删除任何词典 内容未被跟踪。
该模块提供了python函数
is_tracked(obj)
,其中 返回对象的当前跟踪状态。随后 垃圾收集可能会更改对象的跟踪状态。 问题#4688
中引入了某些容器的未跟踪,并且针对问题#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
创建 new 类时会出现一些问题,因此GC应检查子类。
这可能是代码的问题,如:
class MyTuple(namedtuple('A', 'a b')):
# whatever code you want
pass
因为MyTuple
类需要不是不可变的,所以GC应该检查该类是否是namedtuple
的直接子类安全。但是我很确定这种情况有变通方法。
他们可能没有,因为namedtuple
是标准库的一部分,而不是python核心。也许开发人员不想让核心依赖于标准库的模块。
所以,回答你的问题:
namedtuple
答案 1 :(得分:3)
@Bakunu给出了一个很好的答案 - 接受它: - )
这里的光泽:没有未跟踪的噱头是“免费的”:在运行时和维护棘手代码的爆炸中都有实际成本。基本元组和字典类型非常大量使用,无论是用户程序还是CPython实现,并且经常可以解决它们。所以特殊套管它们值得一些痛苦,并且有利于“几乎所有”程序。虽然可以找到可以从未跟踪namedtuple
(或......)中获益的程序示例,但它不会使CPython实现或大多数用户程序受益。但它会对所有程序强加成本(gc代码中的更多条件要求“这是namedtuple
”等等。
请注意,所有容器对象都受益于CPython的“世代”循环gc噱头:给定容器存活的集合越多,容器扫描的频率就越低(因为容器被移动到“老一代”,扫描较少)经常)。因此,除非容器类型大量出现(通常是元组,对于dicts很少是真的)或者容器包含大量对象(通常对于dicts,对于元组很少是真的),因此潜在的增益很少