Python笔记:weakref模块

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

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

一、 创建弱引用

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

一旦你有了一个对象的弱引用,你就能通过调用弱引用来获取被弱引用的对象。下面的例子创建了一个对socket对象的弱引用:

>>> from socket import *
>>> import weakref
>>> s=socket(AF_INET,SOCK_STREAM)
>>> ref=weakref.ref(s)
>>> s
<socket._socketobject instance at 007B4A94>
>>> ref
<weakref at 0x81195c; to 'instance' at 0x7b4a94> 
>>> ref()    #调用它来访问被引用的对象
<socket.socketobject instance at 007B4A94>

一旦没有了对这个对象的其它的引用,调用弱引用将返回None,因为Python已经销毁了这个对象。 注意:大部分的对象不能通过弱引用来访问。

weakref模块中的getweakrefcount(obj)和getweakrefs(obj)分别返回弱引用数和关于所给对象的引用列表。

弱引用对于创建对象(这些对象很费资源)的缓存是有用的。

二、创建代理对象

代理对象是弱引用对象,它们的行为就像它们所引用的对象,这就便于你不必首先调用弱引用来访问背后的对象。通过weakref模块的proxy(obj[,callback])函数来创建代理对象。使用代理对象就如同使用对象本身一样:

>>> from socket import*
>>> import weakref
>>> s=socket(AF_INET,SOCK_STREAM)
>>> ref=weakref.proxy(s)
>>> s
<socket._socketobject instance at 007E4874>
>>> ref 
<socket._socketobject instance at 007E4874>
>>> ref.close() #对象的方法同样工作

callback参数的目的和ref函数相同。在Python删除了一个引用的对象之后,使用代理将会导致一个weakref.ReferenceError错误:

>>> def s
>>> ref
Traceback (most recent call last):
  File "<stdin>", line 1, in ?

弱引用使用的机会不是很多,一般用来进行 cache 编程。我们可以使用 weakref.ref() 来创建一个弱引用。

>>>> import sys
>>> import weakref
>>> class Class1:
  def test(self):
    print "test..."
    
>>> o = Class1()
>>> 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>

weakref 还提供了一个 proxy():

import sys
import weakref
class Class1:
 def test(self):
 print "test"
 
def callback(self):
 print "callback"
 
o = Class1()
p = weakref.proxy(o, callback)
p.test()
o = None

python的弱引用指引用一个对象但不增加它的引用计数器。这么做的好处是什么呢?什么时候需要考虑用若引用呢?

假设我们在设计一个游戏,有一个角色类Char,我们要给他添加一个效果(比如中毒),于是设计了一个效果类Effect。现在,给角色增加效果看上去就像这样:

 
 
  1. char.effect = Effect() # 给角色添加一个效果

每个效果生效的时机都是不同的,为了方便复用,我们再设计一个激活策略类ActivePloy,负责激活效果。于是在Effect和ActivePloy的内部看上去就像这样:

 
 
  1. class Effect(object):   
  2.     def __init__(self):   
  3.         self.active_ploy = ActivePloy(self
  4.  
  5.     def active(self): 
  6.         """激活时的处理""" 
  7.         pass 
  8.  
  9.  
  10. class ActivePloy(object): 
  11.     def __init__(self, effect): 
  12.         self.effect = effect 
  13.  
  14.     def active(self): 
  15.         """激活时,激活对应效果""" 
  16.         self.effect.active() 

这样做的好处是Effect不用关心自己何时激活,激活的判断都放给ActivePloy来处理。看上去挺好的,但是,这里面有一个问题,就是当我们试图给玩家去掉这个效果时……

 
 
  1. del char.effect 

仔细想想,这么干以后,Effect的实例其实是没有被回收的,因为Effect和ActivePloy交叉引用,他们的引用计数都为1。

那么我们为了干净的删除effect,似乎就只能手动的来清理一下他们之间的这个交叉引用了:

 
 
  1. class Effect(object):    
  2.     def __init__(self):    
  3.         self.active_ploy = ActivePloy(self)  
  4.   
  5.     def active(self):  
  6.         """激活时的处理"""  
  7.         pass  
  8.   
  9.     def destroy(self): 
  10.         self.active_ploy.destroy() 
  11.   
  12. class ActivePloy(object):  
  13.     def __init__(self, effect):  
  14.         self.effect = effect  
  15.   
  16.     def active(self):  
  17.         """激活时,激活对应效果"""  
  18.         self.effect.active() 
  19.  
  20.     def destroy(self): 
  21.         self.effect = None 

于是我们要删除一个效果,就得这样:

 
 
  1. char.effect.destroy() 
  2. del char.effect 

太麻烦了,不是吗?而且万一一个效果有多个激活策略的话,必须保证Effect把每个ActivePloy的destroy方法都运行一遍,漏了一个都无法保证自身被干净的删除。

我们来分析一下,之所以这么麻烦,就是因为ActivePloy对Effect有一个引用。那么如果ActivePloy不引用Effect不就OK了?这个时候,让我们来试试弱引用。

 
 
  1. import weakref 
  2. class Effect(object):    
  3.     def __init__(self):    
  4.         self.active_ploy = ActivePloy(self)  
  5.   
  6.     def active(self):  
  7.         """激活时的处理"""  
  8.         pass  
  9.   
  10.   
  11. class ActivePloy(object):  
  12.     def __init__(self, effect):  
  13.         self.effect = weakref.proxy(effect) # 弱引用effect 
  14.   
  15.     def active(self):  
  16.         """激活时,激活对应效果"""  
  17.         self.effect.active() 

代码只有一个地方改变了,就是

 
 
  1. self.effect = weakref.proxy(effect) 

这句的效果就是self.effect可以像往常一样的使用,但是却不会增加effect的引用计数器。换言之,这样写,他们之间的交叉关系消失了!这个时候我们只需要单纯的删掉char.effect,Effect和ActivePloy的实例都会被销毁。

什么,假设ActivePloy在其他地方也被引用了?这样当然只有effect会被销毁。但是我们想让ActivePloy必然随着Effect的销毁而销毁,怎么办呢?那么我们可以改改,给弱引用加上一个回调函数:

 
 
  1. class ActivePloy(object):   
  2.     def __init__(self, effect):   
  3.         self.effect = weakref.proxy(effect, self.on_effect_destroy) # 弱引用effect  
  4.    
  5.     def active(self):   
  6.         """激活时,激活对应效果"""   
  7.         self.effect.active()  
  8.  
  9.     def on_effect_destroy(self, effect): 
  10.         """ 
  11.         effect销毁时会调用这个方法,在这里把对自己的引用都清理干净吧 
  12.         """ 
  13.         pass 

这样一来,就不用担心删不干净了。

本文后半部分出自 “业余码农” 博客,请务必保留此出处http://sleepd.blog.51cto.com/3034090/1073044






  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值