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

缓存对象

refproxy 类被认为是「低级的」,虽然他们在维护单个对象弱引用和允许垃圾回收器回收循环引用方面非常有用,WeakKeyDictionaryWeakValueDictionary 类为创建多个对象的缓存提供了更加合适的 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 来说是很难保证这一点的,因为程序在迭代过程中执行的动作可能导致字典中的数据 「神奇的」消失(垃圾回收器的副作用)。


参考

本文章首发在 LearnKu.com 网站上。
上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~