Python3標准庫:weakref對象的非永久引用


1. weakref對象的非永久引用

weakref模塊支持對象的弱引用。正常的引用會增加對象的引用數,並避免它被垃圾回收。但結果並不總是如期望中的那樣,比如有時可能會出現一個循環引用,或者有時需要內存時可能要刪除對象的緩存。弱引用(weak reference)是一個不能避免對象被自動清理的對象句柄。

1.1 引用

對象的弱引用要通過ref類來管理。要獲取原對象,可以調用引用對象。

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。

1.2 引用回調 

ref構造函數接受一個可選的回調函數,刪除所引用的對象時會調用這個函數。

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())

當引用已經“死亡”而且不再引用原對象時,這個回調會接受這個引用對象作為參數。這個特性的一種用法就是從緩存中刪除弱引用對象。

1.3 最終化對象

清理弱引用時要對資源完成更健壯的管理,可以使用finalize將回調與對象關聯。finalize實例會一直保留(直到所關聯的對象被刪除) ,即使應用並沒有保留最終化對象的引用。

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的參數包括要跟蹤的對象,對象被垃圾回收時要調用的callable,以及傳入這個callable的所有位置或命名參數。

這個finalize實例有一個可寫屬性atexit,用來控制程序退出時是否調用這個回調(如果還未調用)。 

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設置為false會禁用這種行為。

如果向finalize實例提供所跟蹤對象的一個引用,這便會導致一個引用被保留,所以這個對象永遠不會被垃圾回收。 

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對垃圾回收器可見。

使用所跟蹤對象的一個綁定方法作為callable也可以適當地避免對象最終化。

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提供的callable是實例obj的一個綁定方法,所以最終化方法保留了obj的一個引用,它不能被刪除和被垃圾回收。

1.4 代理

有時使用代理比較弱引用更方便。使用代理可以像使用原對象一樣,而且不要求在訪問對象之前先調用代理。這說明,可以將代理傳遞到一個庫,而這個庫並不知道它接收的是一個引用而不是真正的對象。

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異常。

1.5 緩存對象

ref和proxy類被認為是“底層”的。盡管它們對於維護單個對象的弱引用很有用,並且還支持對循環引用的垃圾回收,但WeakKeyDictionary和WeakValueDictionary類為創建多個對象的緩存提供了一個更適合的API。

WeakValueDictionary類使用它包含的值的弱引用,當其他代碼不再真正使用這些值時,則允許垃圾回收。利用垃圾回收器的顯式調用,下面展示了使用常規字典和WeakValueDictionary完成內存處理的區別。 

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):
    # hold objects so any weak references
    # are not removed immediately
    all_refs = {}
    # create the cache using the factory
    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  # decref

    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  # decref

    # remove all references to the objects except the cache
    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變量用來保存引用,以防止它們被過早地垃圾回收。

WeakKeyDictionary的工作與之類似,不過使用了字典中鍵的弱引用而不是值的弱引用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM