Python中的弱引用

77
我一直在尝试理解Python弱引用列表/字典的工作原理。我已经阅读了相关文档,但是我仍然无法理解它们的工作原理以及可以用它们做什么。有人可以给我一个基本示例来说明它们的作用,并解释一下它们的工作原理吗?
(编辑)使用Thomas的代码,当我将obj替换为[1,2,3]时,它会抛出异常:
Traceback (most recent call last):
File "C:/Users/nonya/Desktop/test.py", line 9, in <module>
r = weakref.ref(obj)
TypeError: cannot create weak reference to 'list' object
4个回答

94

理论

引用计数通常工作方式如下:每次你创建一个对对象的引用时,它会增加一个引用计数,每当你删除一个引用时,它会减少一个引用计数。

弱引用允许你创建对对象的引用,但不会增加引用计数。

当 Python 的垃圾收集器运行时,引用计数被使用:任何引用计数为 0 的对象都将被垃圾回收。

你可以使用弱引用来引用昂贵的对象,或避免循环引用(虽然垃圾收集器通常会自动处理)。

用法

下面是一个演示其用法的实际例子:

import weakref
import gc

class MyObject(object):
    def my_method(self):
        print 'my_method was called!'

obj = MyObject()
r = weakref.ref(obj)

gc.collect()
assert r() is obj #r() allows you to access the object referenced: it's there.

obj = 1 #Let's change what obj references to
gc.collect()
assert r() is None #There is no object left: it was gc'ed.

4
列表/字典上的工作原理是怎样的,虽然这是有关类/函数的一个很好的例子。 :) - IT Ninja
2
请您看一下我的修改。当替换列表或字典时,它会抛出那个错误。 - IT Ninja
5
Python的weakref文档建议通过子类化来创建一个弱引用到列表:class WeakRefableList(list):pass。然而,我不确定这是否能帮助您实现想要做的事情,适当的作用域应该足以让垃圾回收处理您的问题。如果您想要跟踪内存泄漏,您可以随时使用gc模块。 - Thomas Orozco
@ThomasOrozco 谢谢,但是你说这个 weakref 不会增加引用计数。那么程序如何知道 'obj' 是否存在,没有引用计数呢?我无法理解 'Usage' 代码中的要点。 - Chan Kim
弱引用允许您创建对对象的引用,而不会增加引用计数。这听起来有些不合理。我真的没有看懂。这意味着引用计数将始终为0。:) 这是不可能的,所以我没有理解措辞。我的理解是引用计数可以增加,并且当删除的引用为0时,GC开始工作。这样对吗? - user1585204
显示剩余3条评论

55

只是想指出,weakref.ref不能用于内置列表,因为在列表的__slots__中没有__weakref__。例如,下面的代码定义了一个支持weakref的列表容器。

import weakref

class weaklist(list):
    __slots__ = ('__weakref__',)

l = weaklist()
r = weakref.ref(l)

2
哇..我经常使用__slots__,这让我避免了一些严重的麻烦 :) - Markus

16

关键在于它们允许引用对象而不阻止垃圾收集。

你想要这样做的两个主要原因是,一是你自己定期管理资源,例如关闭文件,但由于这些间隔时间可能很长,所以垃圾收集器可能会为你做这件事;或者你创建了一个对象,但追踪它在程序中的位置可能相对较昂贵,但你仍然希望处理实际存在的实例。

第二种情况可能更常见——当你持有例如通知对象列表时,并且不想通知系统阻止垃圾回收时,就适用这种方法。


1
也许有3个原因?第三个是破除循环引用的问题。 - dashesy
2
@dashesy 是正确的。弱引用最常见的(可以说是唯一安全的)用例是显式地打破引用循环。虽然 Python 垃圾收集器最终会自动打破这种循环,但防止这种循环形成的空间和时间开销要小得多。其他所有使用弱引用的方式都极其脆弱,在任何情况下都应该以一定程度的恐惧和尊重对待。 - Cecil Curry

4

这里是一个比较dictWeakValueDictionary的例子:

class C: pass
ci=C()
print(ci)

wvd = weakref.WeakValueDictionary({'key' : ci})
print(dict(wvd), len(wvd)) #1
del ci
print(dict(wvd), len(wvd)) #0

ci2=C()
d=dict()
d['key']=ci2
print(d, len(d))
del ci2
print(d, len(d))

这是输出结果:

<__main__.C object at 0x00000213775A1E10>
{'key': <__main__.C object at 0x00000213775A1E10>} 1
{} 0
{'key': <__main__.C object at 0x0000021306B0E588>} 1
{'key': <__main__.C object at 0x0000021306B0E588>} 1

请注意,在第一种情况下,一旦我们使用del ci删除了对象,实际上它也将从字典wvd中删除。

在常规Python字典dict类的情况下,尽管我们尝试删除对象,但是如示例所示,它仍然存在。


注意:如果我们使用del,则不需要在此之后调用gc.collect(),因为仅使用del就可以有效地删除该对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接