參考
1.weakref – Garbage-collectable references to objects
2.Python弱引用介紹
和許多其它的高階語言一樣,Python使用了垃圾回收器來自動銷燬那些不再使用的物件。每個物件都有一個引用計數,當這個引用計數為0時Python能夠安全地銷燬這個物件。
引用計數會記錄給定物件的引用個數,並在引用個數為零時收集該物件。由於一次僅能有一個物件被回收,引用計數無法回收迴圈引用的物件。
一組相互引用的物件若沒有被其它物件直接引用,並且不可訪問,則會永久存活下來。一個應用程式如果持續地產生這種不可訪問的物件群組,就會發生記憶體洩漏。
在物件群組內部使用弱引用(即不會在引用計數中被計數的引用)有時能避免出現引用環,因此弱引用可用於解決迴圈引用的問題。
在計算機程式設計中,弱引用,與強引用相對,是指不能確保其引用的物件不會被垃圾回收器回收的引用。一個物件若只被弱引用所引用,則可能在任何時刻被回收。弱引用的主要作用就是減少迴圈引用,減少記憶體中不必要的物件存在的數量。
使用weakref模組,你可以建立到物件的弱引用,Python在物件的引用計數為0或只存在物件的弱引用時將回收這個物件。
建立弱引用
你可以通過呼叫weakref模組的ref(obj[,callback])來建立一個弱引用,obj是你想弱引用的物件,callback是一個可選的函式,當因沒有引用導致Python要銷燬這個物件時呼叫。回撥函式callback要求單個引數(弱引用的物件)。
一旦你有了一個物件的弱引用,你就能通過呼叫弱引用來獲取被弱引用的物件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
>>>> import sys >>> import weakref >>> class Man: def __init__(self,name): print self.name = name >>> o = Man('Jim') >>> sys.getrefcount(o) 2 >>> r = weakref.ref(o) # 建立一個弱引用 >>> sys.getrefcount(o) # 引用計數並沒有改變 2 >>> r <weakref at 00D3B3F0; to 'instance' at 00D37A30> # 弱引用所指向的物件資訊 >>> o2 = r() # 獲取弱引用所指向的物件 >>> o is o2 True >>> sys.getrefcount(o) 3 >>> o = None >>> o2 = None >>> r # 當物件引用計數為零時,弱引用失效。 <weakref at 00D3B3F0; dead>de> |
上面的程式碼中,我們使用sys包中的getrefcount()
來檢視某個物件的引用計數。需要注意的是,當使用某個引用作為引數,傳遞給getrefcount()
時,引數實際上建立了一個臨時的引用。因此,getrefcount()所得到的結果,會比期望的多1。
一旦沒有了對這個物件的其它的引用,呼叫弱引用將返回None,因為Python已經銷燬了這個物件。 注意:大部分的物件不能通過弱引用來訪問。
weakref模組中的getweakrefcount(obj)和getweakrefs(obj)分別返回弱引用數和關於所給物件的引用列表。
弱引用對於建立物件(這些物件很費資源)的快取是有用的。
建立代理物件
代理物件是弱引用物件,它們的行為就像它們所引用的物件,這就便於你不必首先呼叫弱引用來訪問背後的物件。通過weakref模組的proxy(obj[,callback])函式來建立代理物件。使用代理物件就如同使用物件本身一樣:
1 2 3 4 5 6 7 8 9 10 11 12 |
import weakref class Man: def __init__(self, name): self.name = name def callback(self): print "callback" o = Man('Jim') p = weakref.proxy(o, callback) p.test() o = None |
callback引數的目的和ref函式相同。在Python刪除了一個引用的物件之後,使用代理將會導致一個weakref.ReferenceError錯誤。
迴圈引用
前面說過,使用弱引用,可以解決迴圈引用不能被垃圾回收的問題。
首先我們看下常規的迴圈引用,先建立一個簡單的Graph類,然後建立三個Graph例項:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# -*- coding:utf-8 -*- import weakref import gc from pprint import pprint class Graph(object): def __init__(self, name): self.name = name self.other = None def set_next(self, other): print "%s.set_next(%r)" % (self.name, other) self.other = other def all_nodes(self): yield self n = self.other while n and n.name !=self.name: yield n n = n.other if n is self: yield n return def __str__(self): return "->".join(n.name for n in self.all_nodes()) def __repr__(self): return "<%s at 0x%x name=%s>" % (self.__class__.__name__, id(self), self.name) def __del__(self): print "(Deleting %s)" % self.name def collect_and_show_garbage(): print "Collecting..." n = gc.collect() print "unreachable objects:", n print "garbage:", pprint(gc.garbage) def demo(graph_factory): print "Set up graph:" one = graph_factory("one") two = graph_factory("two") three = graph_factory("three") one.set_next(two) two.set_next(three) three.set_next(one) print print "Graph:" print str(one) collect_and_show_garbage() print three = None two = None print "After 2 references removed" print str(one) collect_and_show_garbage() print print "removeing last reference" one = None collect_and_show_garbage() gc.set_debug(gc.DEBUG_LEAK) print "Setting up the cycle" print demo(Graph) print print "breaking the cycle and cleaning up garbage" print gc.garbage[0].set_next(None) while gc.garbage: del gc.garbage[0] print collect_and_show_garbage() |
這裡使用了python的gc庫的幾個方法, 解釋如下:
- gc.collect() 收集垃圾
- gc.garbage 獲取垃圾列表
- gc.set_debug(gc.DBEUG_LEAK) 列印無法看到的物件資訊
執行結果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
Setting up the cycle Set up graph: one.set_next(<Graph at 0x25c9e70 name=two>) two.set_next(<Graph at 0x25c9e90 name=three>) three.set_next(<Graph at 0x25c9e50 name=one>) Graph: one->two->three->one Collecting... unreachable objects:g 0 garbage:[] After 2 references removed one->two->three->one Collecting... unreachable objects: 0 garbage:[] removeing last reference Collecting... unreachable objects: 6 garbage:[<Graph at 0x25c9e50 name=one>, <Graph at 0x25c9e70 name=two>, <Graph at 0x25c9e90 name=three>, {'name': 'one', 'other': <Graph at 0x25c9e70 name=two>}, {'name': 'two', 'other': <Graph at 0x25c9e90 name=three>}, {'name': 'three', 'other': <Graph at 0x25c9e50 name=one>}] breaking the cycle and cleaning up garbage one.set_next(None) (Deleting two) (Deleting three) (Deleting one) Collecting... unreachable objects: 0 garbage:[] None [Finished in 0.4s]c: uncollectable <Graph 025C9E50> gc: uncollectable <Graph 025C9E70> gc: uncollectable <Graph 025C9E90> gc: uncollectable <dict 025D3030> gc: uncollectable <dict 025D30C0> gc: uncollectable <dict 025C1F60> |
從結果中我們可以看出,即使我們刪除了Graph例項的本地引用,它依然存在垃圾列表中,不能回收。
接下來建立使弱引用的WeakGraph類:
1 2 3 4 5 6 7 8 |
class WeakGraph(Graph): def set_next(self, other): if other is not None: if self in other.all_nodes(): other = weakref.proxy(other) super(WeakGraph, self).set_next(other) return demo(WeakGraph) |
結果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
Setting up the cycle Set up graph: one.set_next(<WeakGraph at 0x23f9ef0 name=two>) two.set_next(<WeakGraph at 0x23f9f10 name=three>) three.set_next(<weakproxy at 023F8810 to WeakGraph at 023F9ED0>) Graph: one->two->three Collecting... unreachable objects:Traceback (most recent call last): File "D:\apps\platform\demo\demo.py", line 87, in <module> gc.garbage[0].set_next(None) IndexError: list index out of range 0 garbage:[] After 2 references removed one->two->three Collecting... unreachable objects: 0 garbage:[] removeing last reference (Deleting one) (Deleting two) (Deleting three) Collecting... unreachable objects: 0 garbage:[] breaking the cycle and cleaning up garbage [Finished in 0.4s with exit code 1] |
上面的類中,使用代理來指示已看到的物件,隨著demo()刪除了物件的所有本地引用,迴圈會斷開,這樣垃圾回收期就可以將這些物件刪除。
因此我們我們在實際工作中如果需要用到迴圈引用的話,儘量採用弱引用來實現。
快取物件
ref
和proxy
都只可用與維護單個物件的弱引用,如果想同時建立多個物件的弱引用咋辦?這時可以使用WeakKeyDictionary
和WeakValueDictionary
來實現。
WeakValueDictionary
類,顧名思義,本質上還是個字典型別,只是它的值型別是弱引用。當這些值引用的物件不再被其他非弱引用物件引用時,那麼這些引用的物件就可以通過垃圾回收器進行回收。
下面的例子說明了常規字典與WeakValueDictionary
的區別。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# -*- coding:utf-8 -*- import weakref import gc from pprint import pprint gc.set_debug(gc.DEBUG_LEAK) class Man(object): def __init__(self, name): self.name = name def __repr__(self): return '<Man name=%s>' % self.name def __del__(self): print "deleting %s" % self def demo(cache_factory): all_refs = {} print "cache type:", cache_factory cache = cache_factory() for name in ["Jim", 'Tom', 'Green']: man = Man(name) cache[name] = man all_refs[name] = man del man print "all_refs=", pprint(all_refs) print print "before, cache contains:", cache.keys() for name, value in cache.items(): print "%s = %s" % (name, value) print "\ncleanup" del all_refs gc.collect() print print "after, cache contains:", cache.keys() for name, value in cache.items(): print "%s = %s" % (name, value) print "demo returning" return demo(dict) print demo(weakref.WeakValueDictionary) |
結果如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
cache type: <type 'dict'> all_refs={'Green': <Man name=Green>, 'Jim': <Man name=Jim>, 'Tom': <Man name=Tom>} before, cache contains: ['Jim', 'Green', 'Tom'] Jim = <Man name=Jim> Green = <Man name=Green> Tom = <Man name=Tom> cleanup after, cache contains: ['Jim', 'Green', 'Tom'] Jim = <Man name=Jim> Green = <Man name=Green> Tom = <Man name=Tom> demo returning deleting <Man name=Jim> deleting <Man name=Green> deleting <Man name=Tom> cache type: weakref.WeakValueDictionary all_refs={'Green': <Man name=Green>, 'Jim': <Man name=Jim>, 'Tom': <Man name=Tom>} before, cache contains: ['Jim', 'Green', 'Tom'] Jim = <Man name=Jim> Green = <Man name=Green> Tom = <Man name=Tom> cleanup deleting <Man name=Jim> deleting <Man name=Green> after, cache contains: [] demo returning [Finished in 0.3s] |