Python的弱引用

发布时间:2019-09-18 07:27:56编辑:auto阅读(1963)

    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 

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

关键字