3.9. weakref — 实现对象的弱引用
用途:提供对象的引用,当没有其他强引用时,对象会被垃圾回收。
weakref
模块提供了对象的弱引用。普遍的引用会增加对象的引用计数,使得它不被垃圾回收。但当可能存在循环引用,或内存需要删除缓存对象时,我们不希望这种情形发生。而弱引用不会阻碍对象被自动回收。
引用
ref
类提供对弱引用的操作,可以直接调用引用来操作原始对象。
weakref_ref.py
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
obj = ExpensiveObject()
r = weakref.ref(obj)
print('obj:', obj)
print('ref:', r)
print('r():', r())
print('deleting obj')
del obj
print('r():', r())
obj
在第二次调用前已经被删除,所以 ref
返回 None
。
$ python3 weakref_ref.py
obj: <__main__.ExpensiveObject object at 0x1007b1a58>
ref: <weakref at 0x1007a92c8; to 'ExpensiveObject' at
0x1007b1a58>
r(): <__main__.ExpensiveObject object at 0x1007b1a58>
deleting obj
(Deleting <__main__.ExpensiveObject object at 0x1007b1a58>)
r(): None
引用回调
ref
构造函数接受一个引用对象被删除时被调用的可选回调函数。
weakref_ref_callback.py
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def callback(reference ):
"""Invoked when referenced object is deleted"""
print('callback({!r})'.format(reference))
obj = ExpensiveObject()
r = weakref.ref(obj, callback)
print('obj:', obj)
print('ref:', r)
print('r():', r())
print('deleting obj')
del obj
print('r():', r())
在引用 「死亡」 并且没有其他引用指向原始对象的时候将引用对象作为参数传递给回调函数。这个功能的一种应用方法是从缓存中删除弱引用对象。
$ python3 weakref_ref_callback.py
obj: <__main__.ExpensiveObject object at 0x1010b1978>
ref: <weakref at 0x1010a92c8; to 'ExpensiveObject' at
0x1010b1978>
r(): <__main__.ExpensiveObject object at 0x1010b1978>
deleting obj
(Deleting <__main__.ExpensiveObject object at 0x1010b1978>)
callback(<weakref at 0x1010a92c8; dead>)
r(): None
终结对象
使用 finalize
来关联对象和回调方法可以更好地在弱引用被清理时管理资源。finalize
实例会一直存在直到关联的对象被删除,即使当前应用没有引用这个终结者实例。
weakref_finalize.py
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def on_finalize(*args):
print('on_finalize({!r})'.format(args))
obj = ExpensiveObject()
weakref.finalize(obj, on_finalize, 'extra argument')
del obj
finalize
方法的参数是,关联的对象、对象回收时的回调方法,以及传递给回调方法的参数。
$ python3 weakref_finalize.py
(Deleting <__main__.ExpensiveObject object at 0x1019b10f0>)
on_finalize(('extra argument',))
finalize
实例的可写属性 atexit
可以控制在应用结束时如果回调方法还没有被调用是否需要执行。
weakref_finalize_atexit.py
import sys
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def on_finalize(*args):
print('on_finalize({!r})'.format(args))
obj = ExpensiveObject()
f = weakref.finalize(obj, on_finalize, 'extra argument')
f.atexit = bool(int(sys.argv[1]))
默认是执行回调方法,可以把 atexit
设成否来禁止这个动用。
$ python3 weakref_finalize_atexit.py 1
on_finalize(('extra argument',))
(Deleting <__main__.ExpensiveObject object at 0x1007b10f0>)
$ python3 weakref_finalize_atexit.py 0
因为 finalize
实例对于对象的关联使得引用被保留,所以对象不会被垃圾回收。
weakref_finalize_reference.py
import gc
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def on_finalize(*args):
print('on_finalize({!r})'.format(args))
obj = ExpensiveObject()
obj_id = id(obj)
f = weakref.finalize(obj, on_finalize, obj)
f.atexit = False
del obj
for o in gc.get_objects():
if id(o) == obj_id:
print('found uncollected object in gc')
如上例所示,即使引用 obj
已经被删除,这个对象还是保留在内存里,因为 f
对于垃圾回收器可见。
$ python3 weakref_finalize_reference.py
found uncollected object in gc
使用对象的绑定方法作为回调方法,也可以阻止对象被垃圾回收。
weakref_finalize_reference_method.py
import gc
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def do_finalize(self):
print('do_finalize')
obj = ExpensiveObject()
obj_id = id(obj)
f = weakref.finalize(obj, obj.do_finalize)
f.atexit = False
del obj
for o in gc.get_objects():
if id(o) == obj_id:
print('found uncollected object in gc')
finalize
里的回调方法是实例 obj
的绑定方法,所以终结实例会持有对 obj
的引用,这个 obj
就不能被删除和垃圾回收。
$ python3 weakref_finalize_reference_method.py
found uncollected object in gc
代理
某种情况下使用代理比使用弱引用更加方便。代理可以像原始对象一样使用,并且在对象可访问之前不需要调用。因此它可以代替一个真实的对象传递给一个不知道正在接收一个引用的库。
weakref_proxy.py
import weakref
class ExpensiveObject:
def __init__(self, name):
self.name = name
def __del__(self):
print('(Deleting {})'.format(self))
obj = ExpensiveObject('My Object')
r = weakref.ref(obj)
p = weakref.proxy(obj)
print('via obj:', obj.name)
print('via ref:', r().name)
print('via proxy:', p.name)
del obj
print('via proxy:', p.name)
如果代理在引用对象被删除之后调用,会引发一个 ReferenceError
异常。
$ python3 weakref_proxy.py
via obj: My Object
via ref: My Object
via proxy: My Object
(Deleting <__main__.ExpensiveObject object at 0x1007aa7b8>)
Traceback (most recent call last):
File "weakref_proxy.py", line 30, in <module>
print('via proxy:', p.name)
ReferenceError: weakly-referenced object no longer exists
缓存对象
ref
和 proxy
类被认为是「低级的」,虽然他们在维护单个对象弱引用和允许垃圾回收器回收循环引用方面非常有用,WeakKeyDictionary
和 WeakValueDictionary
类为创建多个对象的缓存提供了更加合适的 API 。
WeakValueDictionary
类使用弱引用去保存它的值,当其他代码没有真正使用它们的时候允许它们被垃圾回收。通过显式的调用垃圾回收器可以看出使用常规字典和 WeakValueDictionary
对内存处理方式的不同:
weakref_valuedict.py
import gc
from pprint import pprint
import weakref
gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
class ExpensiveObject:
def __init__(self, name):
self.name = name
def __repr__(self):
return 'ExpensiveObject({})'.format(self.name)
def __del__(self):
print(' (Deleting {})'.format(self))
def demo(cache_factory):
# 持有对象,以便任何弱引用
# 不会被立即删除
all_refs = {}
# 使用工厂创建缓存
print('CACHE TYPE:', cache_factory)
cache = cache_factory()
for name in ['one', 'two', 'three']:
o = ExpensiveObject(name)
cache[name] = o
all_refs[name] = o
del o # 引用减少一次
print(' all_refs =', end=' ')
pprint(all_refs)
print('\n Before, cache contains:', list(cache.keys()))
for name, value in cache.items():
print(' {} = {}'.format(name, value))
del value # 引用减少一次
# 删掉所有除了缓存的引用
print('\n Cleanup:')
del all_refs
gc.collect()
print('\n After, cache contains:', list(cache.keys()))
for name, value in cache.items():
print(' {} = {}'.format(name, value))
print(' demo returning')
return
demo(dict)
print()
demo(weakref.WeakValueDictionary)
任何值被缓存的循环变量都必须被显式的删除, 这样对象的引用计数才能减少。否则垃圾回收器将不会回收这些对象,这些对象将会一直留在缓存中。同样 all_refs
变量用于持有引用,用来防止对象过早的被垃圾回收器收集。
$ python3 weakref_valuedict.py
CACHE TYPE: <class 'dict'>
all_refs = {'one': ExpensiveObject(one),
'three': ExpensiveObject(three),
'two': ExpensiveObject(two)}
Before, cache contains: ['one', 'three', 'two']
one = ExpensiveObject(one)
three = ExpensiveObject(three)
two = ExpensiveObject(two)
Cleanup:
After, cache contains: ['one', 'three', 'two']
one = ExpensiveObject(one)
three = ExpensiveObject(three)
two = ExpensiveObject(two)
demo returning
(Deleting ExpensiveObject(one))
(Deleting ExpensiveObject(three))
(Deleting ExpensiveObject(two))
CACHE TYPE: <class 'weakref.WeakValueDictionary'>
all_refs = {'one': ExpensiveObject(one),
'three': ExpensiveObject(three),
'two': ExpensiveObject(two)}
Before, cache contains: ['one', 'three', 'two']
one = ExpensiveObject(one)
three = ExpensiveObject(three)
two = ExpensiveObject(two)
Cleanup:
(Deleting ExpensiveObject(one))
(Deleting ExpensiveObject(three))
(Deleting ExpensiveObject(two))
After, cache contains: []
demo returning
WeakKeyDictionary
用法与上面的比较相似,只不过是对字典中的 key 进行弱引用处理,而不是 values 。
警告
WeakValueDictionary
的官方文档包含以下警告:警告: 因为
WeakValueDictionary
是建立在 Python 字典之上的,它在迭代的时候不能改变它自身的大小。但是对于WeakValueDictionary
来说是很难保证这一点的,因为程序在迭代过程中执行的动作可能导致字典中的数据 「神奇的」消失(垃圾回收器的副作用)。
参考
- 标准库里关于 weakref 的文档
gc
--gc
模块是解释器中的垃圾回收器提供的接口。- PEP 205 -- 「弱引用」 增强协议
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。