正是因为有引用,对象才会在内存中存在。当对象的引用数量归零后,垃圾回收程序会把 对象销毁。但是,有时需要引用对象,而不让对象存在的时间超过所需时间。这经常用在缓存中。
弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。
弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。
下面展示如何使用 weakref.ref 实例获取所指对象。如果对象存在,调用弱引用可 以获取对象;否则返回 None。
w = weakref.ref(a) 创建弱引用对象 w,下一行审查它。
调用 w() 返回的是被引用的对象,{0, 1}。因为这是控制台会话,所以 {0, 1} 会绑 定给 _ 变量。
del a a不再指代 {0, 1} 集合,因此集合的引用数量减少了。但是 _ 变量仍然指代它。
调用 w() 依旧返回 {0, 1}。 但是,随后 _ 绑定到结果值 False。现在 {0, 1} 没有强引用了。
因为 {0, 1} 对象不存在了,所以 w() 返回 None
weakref 模块的文档(http://docs.python.org/3/library/weakref.html)指出,weakref.ref 类其 实是低层接口,供高级用途使用,多数程序最好使用 weakref 集合和 finalize。也就是说, 应该使用 WeakKeyDictionary、WeakValueDictionary、WeakSet 和 finalize(在内部使用弱 引用),不要自己动手创建并处理 weakref.ref 实例。在示例中那么做是希望借 助实际使用 weakref.ref 来褪去它的神秘色彩。但是实际上,多数时候 Python 程序都使用 weakref 集合。
WeakValueDictionary
WeakValueDictionary 类实现的是一种可变映射,里面的值是对象的弱引用。被引用的对象 在程序中的其他地方被当作垃圾回收后,对应的键会自动从 WeakValueDictionary 中删除。 因此,WeakValueDictionary 经常用于缓存。
示例:
class Cheese:
"""
weakref 集合
"""
import weakref
class Cheese:
def __init__(self, kind):
self.kind = kind
def __repr__(self):
return 'Cheese(%r)' % self.kind
if __name__ == "__main__":
# 创建 WeakValueDictionary 实例
stock = weakref.WeakValueDictionary()
catalog = [Cheese('Red Leicester'), Cheese('Tilsit'), Cheese('Brie'), Cheese('Parmesan')]
# stock 把奶酪的名称映射到 catalog 中 Cheese 实例的弱引用上。
for cheese in catalog:
stock[cheese.kind] = cheese
# stock 是完整的。
print(sorted(stock.keys()))
# 删除 catalog 之后
del catalog
print(sorted(stock.keys()))
del cheese
print(sorted(stock.keys()))
运行结果:
这里在 del catalog 删除后为啥 stock.keys() 还有值呢?
临时变量引用了对象,这可能会导致该变量的存在时间比预期长。通常,这对 局部变量来说不是问题,因为它们在函数返回时会被销毁。但是在示例中,for 循环中的变量 cheese 是全局变量,除非显式删除,否则不会消失。
与 WeakValueDictionary 对应的是WeakKeyDictionary, 后 者 的 键 是 弱 引 用。weakref. WeakKeyDictionary 的文档: (https://docs.python.org/3/library/weakref.html?highlight=weakref#weakref. WeakKeyDictionary)指出了一些可能的用途:
weakref 模块还提供了 WeakSet 类,按照文档的说明,这个类的作用很简单:“保存元素弱引用的集合类。元素没有强引用时,集合会把它删除。”如果一个类需要知道所有实例, 一种好的方案是创建一个 WeakSet 类型的类属性,保存实例的引用。如果使用常规的 set, 实例永远不会被垃圾回收,因为类中有实例的强引用,而类存在的时间与 Python 进程一样 长,除非显式删除类。这些集合,以及一般的弱引用,能处理的对象类型有限。
弱引用的局限
不是每个 Python 对象都可以作为弱引用的目标(或称所指对象)。基本的 list 和 dict 实 例不能作为所指对象,但是它们的子类可以轻松地解决这个问题
class MyList(list):
"""list的子类,实例可以作为弱引用的目标"""
a_list = MyList(range(10))
# a_list可以作为弱引用的目标 wref_to_a_list = weakref.ref(a_list)
set 实例可以作为所指对象,因此可以使用 set 实例。用户定义的类型也没问题, 但是,int 和 tuple 实例不能作为弱引用的目标,甚至它们的子类也不行。
这些局限基本上是 CPython 的实现细节,在其他 Python 解释器中情况可能不一样。这些局 限是内部优化导致的结果。