从{}到WeakValueDictionary
今天在看关于面试的题,用装饰器实现的时候用的是普通的字典:
from functools import wraps
def single_ton(cls):
_instance = {}
@wraps(cls)
def single(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return single
@single_ton
class SingleTon(object):
val = 123
def __init__(self, a):
self.a = a
if __name__ == '__main__':
s = SingleTon(1)
t = SingleTon(2)
print s is t
print s.a, t.a
print s.val, t.val
我突然想到之前不是学到 weakref
库里面有个弱引用的字典,这里不是正好可以用到吗,于是我改了下代码:
from weakref import WeakValueDictionary
def single_ton(cls):
_instance = WeakValueDictionary()
...
很快啊,直接收获一个报错在脸上
KeyError: <class '__main__.SingleTon'>
百思不得其解,最后通过《Python weakref (弱引用 ) 教程》解惑,_instance[cls] = cls(*args, **kwargs)
这段代码等号后面创建的类实例并没有任何一个强引用,只有 WeakValueDictionary()
中的一个弱引用,于是触发了垃圾回收机制,所以字典实际上是空的,于是我们后面的 return 语句自然就会报错 KeyError。
所以可以把代码改成这样:
def single_ton(cls):
_instance = WeakValueDictionary()
@wraps(cls)
def single(*args, **kwargs):
if cls not in _instance:
cls_instance = cls(*args, **kwargs)
_instance[cls] = cls_instance
return _instance[cls]
return single
或者像《python 设计模式-单例模式》里面调用 setdefault
方法:
def single_ton(cls):
_instance = WeakValueDictionary()
@wraps(cls)
def single(*args, **kwargs):
return _instance.setdefault(cls, cls(*args, **kwargs))
return single
最后,直接用{}和用WeakValueDictionary的区别就在于引用机制。用前者时,因为被字典所引用,无法完全销毁掉已经创建的实例,而后者可以。