Python 弱引用 學習

參考
1.weakref – Garbage-collectable references to objects
2.Python弱引用介紹html

和許多其它的高級語言同樣,Python使用了垃圾回收器來自動銷燬那些再也不使用的對象。每一個對象都有一個引用計數,當這個引用計數爲0時Python可以安全地銷燬這個對象。node

引用計數會記錄給定對象的引用個數,並在引用個數爲零時收集該對象。因爲一次僅能有一個對象被回收,引用計數沒法回收循環引用的對象。python

一組相互引用的對象若沒有被其它對象直接引用,而且不可訪問,則會永久存活下來。一個應用程序若是持續地產生這種不可訪問的對象羣組,就會發生內存泄漏。緩存

在對象羣組內部使用弱引用(即不會在引用計數中被計數的引用)有時能避免出現引用環,所以弱引用可用於解決循環引用的問題。安全

在計算機程序設計中,弱引用,與強引用相對,是指不能確保其引用的對象不會被垃圾回收器回收的引用。一個對象若只被弱引用所引用,則可能在任什麼時候刻被回收。弱引用的主要做用就是減小循環引用,減小內存中沒必要要的對象存在的數量。app

使用weakref模塊,你能夠建立到對象的弱引用,Python在對象的引用計數爲0或只存在對象的弱引用時將回收這個對象。函數

建立弱引用

你能夠經過調用weakref模塊的ref(obj[,callback])來建立一個弱引用,obj是你想弱引用的對象,callback是一個可選的函數,當因沒有引用致使Python要銷燬這個對象時調用。回調函數callback要求單個參數(弱引用的對象)。this

一旦你有了一個對象的弱引用,你就能經過調用弱引用來獲取被弱引用的對象。debug

>>>> 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])函數來建立代理對象。使用代理對象就如同使用對象自己同樣:

import weakref

class Man:
    def __init__(self, name):
        self.name = name
    def test(self):
        print "this is a test!"

def callback(self):
    print "callback"
    
o = Man('Jim')
p = weakref.proxy(o, callback)
p.test()
o=None
p.test()

callback參數的做用和ref函數中callback同樣。在Python刪除了一個引用的對象以後,使用代理將會致使一個weakref.ReferenceError錯誤。

循環引用

前面說過,使用弱引用,能夠解決循環引用不能被垃圾回收的問題。
首先咱們看下常規的循環引用,先建立一個簡單的Graph類,而後建立三個Graph實例:

# -*- 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) 打印沒法看到的對象信息

運行結果以下:

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類:

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)

結果以下:

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()刪除了對象的全部本地引用,循環會斷開,這樣垃圾回收期就能夠將這些對象刪除。

所以咱們咱們在實際工做中若是須要用到循環引用的話,儘可能採用弱引用來實現。

緩存對象

refproxy都只可用與維護單個對象的弱引用,若是想同時建立多個對象的弱引用咋辦?這時可使用WeakKeyDictionaryWeakValueDictionary來實現。

WeakValueDictionary類,顧名思義,本質上仍是個字典類型,只是它的值類型是弱引用。當這些值引用的對象再也不被其餘非弱引用對象引用時,那麼這些引用的對象就能夠經過垃圾回收器進行回收。
下面的例子說明了常規字典與WeakValueDictionary的區別。

# -*- 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)

結果以下所示:

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]
相關文章
相關標籤/搜索