资讯专栏INFORMATION COLUMN

Python 弱引用 学习

philadelphia / 1070人阅读

摘要:引用计数会记录给定对象的引用个数,并在引用个数为零时收集该对象。在对象群组内部使用弱引用即不会在引用计数中被计数的引用有时能避免出现引用环,因此弱引用可用于解决循环引用的问题。

参考
1.weakref – Garbage-collectable references to objects
2.Python弱引用介绍

和许多其它的高级语言一样,Python使用了垃圾回收器来自动销毁那些不再使用的对象。每个对象都有一个引用计数,当这个引用计数为0时Python能够安全地销毁这个对象。

引用计数会记录给定对象的引用个数,并在引用个数为零时收集该对象。由于一次仅能有一个对象被回收,引用计数无法回收循环引用的对象。

一组相互引用的对象若没有被其它对象直接引用,并且不可访问,则会永久存活下来。一个应用程序如果持续地产生这种不可访问的对象群组,就会发生内存泄漏。

在对象群组内部使用弱引用(即不会在引用计数中被计数的引用)有时能避免出现引用环,因此弱引用可用于解决循环引用的问题。

在计算机程序设计中,弱引用,与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则可能在任何时刻被回收。弱引用的主要作用就是减少循环引用,减少内存中不必要的对象存在的数量。

使用weakref模块,你可以创建到对象的弱引用,Python在对象的引用计数为0或只存在对象的弱引用时将回收这个对象。

创建弱引用

你可以通过调用weakref模块的ref(obj[,callback])来创建一个弱引用,obj是你想弱引用的对象,callback是一个可选的函数,当因没有引用导致Python要销毁这个对象时调用。回调函数callback要求单个参数(弱引用的对象)。

一旦你有了一个对象的弱引用,你就能通过调用弱引用来获取被弱引用的对象。

>>>> 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
 # 弱引用所指向的对象信息
>>> o2 = r() # 获取弱引用所指向的对象
>>> o is o2
True
>>> sys.getrefcount(o)
3
>>> o = None
>>> o2 = None
>>> r # 当对象引用计数为零时,弱引用失效。
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()
two.set_next()
three.set_next()

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:[,
 ,
 ,
 {"name": "one", "other": },
 {"name": "two", "other": },
 {"name": "three", "other": }]

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 
gc: uncollectable 
gc: uncollectable 
gc: uncollectable 
gc: uncollectable 
gc: uncollectable 

从结果中我们可以看出,即使我们删除了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()
two.set_next()
three.set_next()

Graph:
one->two->three
Collecting...
unreachable objects:Traceback (most recent call last):
  File "D:appsplatformdemodemo.py", line 87, in 
    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 "" % 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 "
cleanup"
    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: 
all_refs={"Green": , "Jim": , "Tom": }

before, cache contains: ["Jim", "Green", "Tom"]
Jim = 
Green = 
Tom = 

cleanup

after, cache contains: ["Jim", "Green", "Tom"]
Jim = 
Green = 
Tom = 
demo returning
deleting 
deleting 
deleting 

cache type: weakref.WeakValueDictionary
all_refs={"Green": , "Jim": , "Tom": }

before, cache contains: ["Jim", "Green", "Tom"]
Jim = 
Green = 
Tom = 

cleanup
deleting 
deleting 

after, cache contains: []
demo returning

[Finished in 0.3s]

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37994.html

相关文章

  • Python基础系列:初识python引用计数与引用

    摘要:第一次初始化对象,并且用变量来引用,所以这里的引用计数就为。接下来通过创建一个弱引用,通过打印引用计数后,发现计数并没有改变。由于一次仅能有一个对象被回收,引用计数无法回收循环引用的对象。所以弱引用很适合处理这种循环引用的场景。 写在前边: 之前的socket系列就告一段落,主要是对自己所学做一个总结与记录。 接下来我打算基于libevent写一个支持并发的HTTP服务器。因为之前学习...

    robin 评论0 收藏0
  • Python学习之路27-对象引用、可变性和垃圾回收

    摘要:函数的参数作为引用时唯一支持的参数传递模式是共享传参,它指函数的形参获得实参中各个引用的副本,即形参是实参的别名。而在上面这个例子中,类的属性实际上是形参所指向的对象所指对象,的别名。 《流畅的Python》笔记本篇是面向对象惯用方法的第一篇,一共六篇。本篇主要是一些概念性的讨论,内容有:Python中的变量,对象标识,值,别名,元组的某些特性,深浅复制,引用,函数参数,垃圾回收,de...

    Batkid 评论0 收藏0
  • Python中的对象引用、可变性和垃圾回收

    摘要:一对象引用基础知识变量是标注而不是容器。也就是说元组中不可变的是元素的标识,但元组的值会随着引用的可变对象变化而变化。在中每个对象的引用都会有统计。弱引用不会妨碍对象被当做垃圾回收。 导语:本文章记录了本人在学习Python基础之面向对象篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。 本文重点: 1、明确变量保存的是引用这一本质;2、熟悉对象引用的基础知识;...

    ytwman 评论0 收藏0
  • 流畅的python读书笔记-第八章-对象引用、可变性和垃圾回收

    摘要:运算符比较两个对象的标识函数返回对象标识的整数表示。实际上,每个对象都会统计有多少引用指向自己。对象被销毁了,调用了回调,的值变成了。当对象的引用数量归零后,垃圾回收程序会把对象销毁。引用的目标对象称为所指对象。 对象不是个盒子 showImg(https://segmentfault.com/img/bV95mW?w=1784&h=988); class Gizmo: def...

    zgbgx 评论0 收藏0
  • 编写符合Python风格的对象

    摘要:自定义向量类型从自定义向量类型入手写出符合风格的对象,这离不开特殊方法的支持。将对象定为不可变的通过使用两个前导下划线。程序员约定使用一个下划线前缀编写受保护的属性即,他们认为应该使用命名约定来避免意外覆盖属性。 导语:本文章记录了本人在学习Python基础之面向对象篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。 本文重点: 1、掌握编写Pythonic c...

    ethernet 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<