Python WeakValueDictionary 在 IPython 中保留 gc.collect() 之后的值

Python WeakValueDictionary retains values after gc.collect() in IPython

我想了解 Python weakref 模块及其用例,所以我有以下设置:

import gc, weakref

class obj(object):
    def __init__(self, val=None):
        self._s = "Sample" if not val else " ".join(["Sample:", str(val)])
    def sample(self):
        return self._s

ol = [obj(x) for x in range(1,4)]
o1 = obj(1)
o2 = obj(2)
o3 = obj(3)

wdict1 = weakref.WeakValueDictionary({k:ol[k-1] for k in range(1,4)})

wdict2 = weakref.WeakValueDictionary()
wdict2[1] = o1
wdict2[2] = o2
wdict2[3] = o3

在 运行 我的测试之后,我可以清楚地看到,WeakValueDictionary 以某种方式保留了所有值,即使我已明确调用 gc.collect()。根据我的理解,如以下 answer 中所述,调用 gc.collect() 应该删除所有弱引用值。

In [2]: wdict1.items()
Out[2]: 
[(1, <__main__.obj at 0x7fea09c0be90>),
 (2, <__main__.obj at 0x7fea09c0bf10>),
 (3, <__main__.obj at 0x7fea09c0bf50>)]

In [3]: wdict2.items()
Out[3]: 
[(1, <__main__.obj at 0x7fea09c51790>),
 (2, <__main__.obj at 0x7fea09c0bed0>),
 (3, <__main__.obj at 0x7fea09c0bf90>)]

In [4]: del ol[0]

In [5]: del o1

In [6]: gc.collect()
Out[6]: 64

In [7]: wdict1.items()
Out[7]: 
[(1, <__main__.obj at 0x7fea09c0be90>),
 (2, <__main__.obj at 0x7fea09c0bf10>),
 (3, <__main__.obj at 0x7fea09c0bf50>)]

In [8]: wdict2.items()
Out[8]: 
[(1, <__main__.obj at 0x7fea09c51790>),
 (2, <__main__.obj at 0x7fea09c0bed0>),
 (3, <__main__.obj at 0x7fea09c0bf90>)]

In [9]: del ol[0]

In [10]: del o2

In [11]: gc.collect()
Out[11]: 0

In [12]: wdict1.items()
Out[12]: 
[(1, <__main__.obj at 0x7fea09c0be90>),
 (2, <__main__.obj at 0x7fea09c0bf10>),
 (3, <__main__.obj at 0x7fea09c0bf50>)]

In [13]: wdict2.items()
Out[13]: 
[(1, <__main__.obj at 0x7fea09c51790>),
 (2, <__main__.obj at 0x7fea09c0bed0>),
 (3, <__main__.obj at 0x7fea09c0bf90>)]

In [14]: weakref.getweakrefs(ol[0])
Out[14]: [<weakref at 0x7fea0ab05470; to 'obj' at 0x7fea09c0bf50>]

In [15]: weakref.getweakrefs(o3)
Out[15]: [<weakref at 0x7fea09c060b0; to 'obj' at 0x7fea09c0bf90>]

In [16]: wdict1[1].sample()
Out[16]: 'Sample: 1'

In [17]: wdict2[2].sample()
Out[17]: 'Sample: 2'

我的代码有什么问题,为什么所有的弱引用值都被保留了?

问题是 IPython 使用以下 -

保留对您的对象的引用
  1. _ - 最后一条语句的结果。

  2. __ - 倒数第二个语句的结果。

  3. ___ - 倒数第三个语句的结果。

  4. In[num] - 运行 提示编号 num

  5. 的语句的字符串
  6. Out[num]-提示号num语句的result/output。

来自documentation-

Input and output history are kept in variables called In and Out, keyed by the prompt numbers, e.g. In[4]. The last three objects in output history are also kept in variables named _, __ and ___.

您实际上可以尝试 运行ning Out[2] 并查看它是对 wdict1.items() 结果的引用(如果在 2 提示编号中您 运行 你在例子中给出的陈述)。

IPython 很可能保留了很多对您的对象的此类引用,因此当您删除 ol[0]o1 等名称之一,然后执行 gc.collect 。它实际上并不收集对象,因为仍然有对该对象的引用(在 ______Out[num] 中)。

我能想到的两个解决方案 -

  1. 使用%xdel magic command to remove the reference, like %xdel ol[0] , instead of del ol[0] . This would cause IPython to clear out all references it keeps as well. Based on documentation -

Delete a variable, trying to clear it from anywhere that IPython’s machinery has references to it.

  1. 您可以尝试 运行将此测试作为脚本而不是交互地进行,在这种情况下将不会创建这些额外的引用,您应该会看到正确的行为。