python的弱引用指引用一个对象但不增加它的引用计数器。这么做的好处是什么呢?什么时候需要考虑用若引用呢?
假设我们在设计一个游戏,有一个角色类Char,我们要给他添加一个效果(比如中毒),于是设计了一个效果类Effect。现在,给角色增加效果看上去就像这样:
- char.effect = Effect() # 给角色添加一个效果
每个效果生效的时机都是不同的,为了方便复用,我们再设计一个激活策略类ActivePloy,负责激活效果。于是在Effect和ActivePloy的内部看上去就像这样:
- class Effect(object):
- def __init__(self):
- self.active_ploy = ActivePloy(self)
-
- def active(self):
- """激活时的处理"""
- pass
-
-
- class ActivePloy(object):
- def __init__(self, effect):
- self.effect = effect
-
- def active(self):
- """激活时,激活对应效果"""
- self.effect.active()
这样做的好处是Effect不用关心自己何时激活,激活的判断都放给ActivePloy来处理。看上去挺好的,但是,这里面有一个问题,就是当我们试图给玩家去掉这个效果时……
- del char.effect
仔细想想,这么干以后,Effect的实例其实是没有被回收的,因为Effect和ActivePloy交叉引用,他们的引用计数都为1。
那么我们为了干净的删除effect,似乎就只能手动的来清理一下他们之间的这个交叉引用了:
- class Effect(object):
- def __init__(self):
- self.active_ploy = ActivePloy(self)
-
- def active(self):
- """激活时的处理"""
- pass
-
- def destroy(self):
- self.active_ploy.destroy()
-
- class ActivePloy(object):
- def __init__(self, effect):
- self.effect = effect
-
- def active(self):
- """激活时,激活对应效果"""
- self.effect.active()
-
- def destroy(self):
- self.effect = None
于是我们要删除一个效果,就得这样:
- char.effect.destroy()
- del char.effect
太麻烦了,不是吗?而且万一一个效果有多个激活策略的话,必须保证Effect把每个ActivePloy的destroy方法都运行一遍,漏了一个都无法保证自身被干净的删除。
我们来分析一下,之所以这么麻烦,就是因为ActivePloy对Effect有一个引用。那么如果ActivePloy不引用Effect不就OK了?这个时候,让我们来试试弱引用。
- import weakref
- class Effect(object):
- def __init__(self):
- self.active_ploy = ActivePloy(self)
-
- def active(self):
- """激活时的处理"""
- pass
-
-
- class ActivePloy(object):
- def __init__(self, effect):
- self.effect = weakref.proxy(effect) # 弱引用effect
-
- def active(self):
- """激活时,激活对应效果"""
- self.effect.active()
代码只有一个地方改变了,就是
- self.effect = weakref.proxy(effect)
这句的效果就是self.effect可以像往常一样的使用,但是却不会增加effect的引用计数器。换言之,这样写,他们之间的交叉关系消失了!这个时候我们只需要单纯的删掉char.effect,Effect和ActivePloy的实例都会被销毁。
什么,假设ActivePloy在其他地方也被引用了?这样当然只有effect会被销毁。但是我们想让ActivePloy必然随着Effect的销毁而销毁,怎么办呢?那么我们可以改改,给弱引用加上一个回调函数:
- class ActivePloy(object):
- def __init__(self, effect):
- self.effect = weakref.proxy(effect, self.on_effect_destroy) # 弱引用effect
-
- def active(self):
- """激活时,激活对应效果"""
- self.effect.active()
-
- def on_effect_destroy(self, effect):
- """
- effect销毁时会调用这个方法,在这里把对自己的引用都清理干净吧
- """
- pass
这样一来,就不用担心删不干净了。