【问题标题】:Strings not referenced by dicts?字典未引用的字符串?
【发布时间】:2015-09-16 20:32:38
【问题描述】:

看看这段 Python 代码:

from gc import get_referrers as refs
x = 'x'
d = {x:x}
print(d in refs(x))

它打印 False。这本身就很奇怪,但当您考虑以下情况时会变得更加奇怪:

  • 如果 x 是数字(int、float、complex、Fraction、Decimal)而不是字符串,它仍然会打印 False。对于字节和字节数组也是如此。但是对于其他所有类型(如果用作键,则可散列,例如 tuple 或 freezeset - 但还有许多其他类型,如果仅用作值),它会打印 True。

  • 如果 d 是包含 x 的任何其他容器(集合、列表、元组...),则打印 True。仅对于 dict,它会打印 False。此外,x 是键还是值,或者同上,两者都没有关系。

我认为在 Python 中每个对象都是一个引用(与具有原始类型的 Java 或 Ruby 的值类型小整数相反),但现在似乎 str 和 int 有点原始类型,而不是参考。但另一方面,为什么只在 dicts 中??

我也知道从 -5 到 256 的整数被缓存在 CPython 中(并且可以保留小字符串),所以不要引用它们是有意义的,因为它们无论如何都不会被删除,但这适用于任何整数(和长字符串)我试过了,比那个范围大得多。

有人知道这里发生了什么吗?

--- 更新 ---

好奇者和好奇者......似乎 datetime.{datetime, date, time} 类具有相同的“未引用”行为。现在,我知道 AnyStr 和 Number 有一个共同点:它们的哈希值是使用每个会话的盐随机化的。但这没有任何意义,因为即使这些只是dicts中的值,而不是键,也会观察到这种行为。并且值没有被散列。还是他们?

【问题讨论】:

  • 有趣的是,引用计数 (sys.getrefcount) 在分配字典后确实增加了 2。但是get_referrers 甚至会在键和值不同时返回 False(其中一个是 x),因此它似乎不能防止循环引用。
  • 是的,这是我的假设之一,但正如你所说,它不成立,因为 x 不必既是键又是值。而其他容器(列表)可以有循环引用,它们不会表现出这种行为。
  • 顺便说一句,Python 2.7 和 3.4 中的症状相似,引用计数的总数略有不同,这是意料之中的。

标签: python string dictionary reference cpython


【解决方案1】:

来自gcmodule.c:

某些类型的容器不能参与引用循环,并且 所以不需要被垃圾收集器跟踪。取消跟踪这些 对象降低了垃圾收集的成本。然而,确定 哪些对象可能未被跟踪不是免费的,而且成本必须是 权衡垃圾收集的好处。

...

只包含不可变对象的字典也不需要 跟踪。创建时不跟踪字典。如果跟踪的项目是 插入字典(作为键或值),字典 被跟踪。在完整的垃圾收集(所有代)期间, 收集器将取消跟踪任何内容不是 跟踪。

基本上,由于 Python 中的对象是引用计数的,垃圾收集器的目标是打破引用循环,其他未引用的对象在最后一个引用消失时被销毁。为了优化,垃圾收集器不会跟踪某些根本无法参与引用循环的对象。

所以,字符串被引用。但是,垃圾收集器对这些字典根本不感兴趣,因此gc.get_referrers() 没有列出它们。

【讨论】:

  • 刚刚在源代码中也发现了,你打败了我。有趣的是,如果值是不可变的(键不能是),则该值出现在get_referrers() 中,但键永远不会出现,这可能与引用相矛盾(尽管它说 dictionary 已被跟踪,不是关键)。
  • 清除最后一条评论。如果(我猜)缓存了密钥,则不会跟踪密钥。将x 更改为“Slartibartfast”并将值更改为[1,2,3,4],在gc.get_referrers() 中提供键和值作为跟踪。显然这里有一些魔力。
  • 似乎抓住源头对灵魂真的有好处,正如某个拉里沃尔曾经说过的那样。 :-) 谢谢。但是我仍然不明白为什么只有字典而不是集合……就好像集合真的是 Python 中的二等对象。 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-11
  • 1970-01-01
相关资源
最近更新 更多