本篇文章的内容主要包含
- 利用Python弱引用存储字典缓存类的实例,让参数相同的实例不用重复生成
- 略过复杂的通用化代码编写,利用Python自带库来缓存实例和方法对象
在Python的许多库中都有缓存实例的例子,比如logging模块的Logger类实例
import logginga = logging.getLogger("abc")
b = logging.getLogger("abc")print(a is b) # True
弱引用(weakref)通常用于缓存或映射数据量较大的对象,当你使用python字典存放例如key为name,value为一个很大的图像对象,或者反过来,引用和修改这个图像对象时始终是“实时的”,因为它实际的存在字典中。但是利用弱引用中的WeakKeyDictionary
和WeakValueDictionary
,你引用这个图像时只需要使用它的名字即可,它不需要占用很大的空间来存储,实际上弱引用就是一个对象指针。
创建实例缓存的一种方式是写一个方法,每次都通过这个方法访问实例
import weakrefclass Person:def __init__(self, name):print("person initializing")self.name = name_cache_instance = weakref.WeakValueDictionary() # 这里应该使用全局变量,因为是共用的存储对象def get_person(name):if name not in _cache_instance:instance = Person(name)_cache_instance[name] = instancereturn instanceelse:return _cache_instance[name]def test():q = get_person("q")e = get_person("q")print(q is e)r = get_person("q")print(e is r)if __name__ == '__main__':test()
输出
person initializing
True
True
从输出可以看出3次调用参数相同的Person只实例了一次
看到这里很容易联想到使用装饰器来简化代码
import weakref
from functools import wraps_cache_instance = weakref.WeakValueDictionary() def instance_cache(cls_instance):@wraps(cls_instance)def inner(name, *args, **kwargs):if name not in _cache_instance:instance = cls_instance(name, *args, **kwargs)_cache_instance[name] = instanceelse:instance = _cache_instance[name]return instancereturn inner@instance_cache
class Person:def __init__(self, name):print("person initializing")self.name = namedef test():q = Person("abc")e = Person("abc")print(q is e)r = Person("abc")print(e is r)if __name__ == '__main__':test()
输出
person initializing
True
True
输出在期望之内,但这个装饰器要实现到更通用化还是不够,想要把存入的参数通用化作为键并且适用于不同的类实例和方法并不是一件简单的事情,幸好Python为我们提供了一个非常好用和方便的最近最久未使用的缓存方法functools.lru_cache
from functools import lru_cache@lru_cache()
class Person:def __init__(self, fist_name, last_name=None):print("person initializing")self.first_name = fist_nameself.last_name = last_namedef set_last_name(self, name):self.last_name = namedef test():# 测试相同参数的实例是否会重复生成p1 = Person("abc", "d")p2 = Person("abc", "d")print(p1 is p2)p3 = Person("abc", "d")print(p2 is p3)def test1():# 测试较复杂的参数的实例是否会重复生成import jsond1 = {"a": [1, 2], "b": "3"}d2 = {"a": [1, 2], "b": "3"}d3 = {"a": [1, 2], "b": "3"}d1 = json.dumps(d1)d2 = json.dumps(d2)d3 = json.dumps(d3)p1 = Person("abc", d1)p2 = Person("abc", d2)print(p1 is p2)p3 = Person("abc", d3)print(p2 is p3)def test2():# 测试实例发生改变后的情况p1 = Person("abc")p2 = Person("abc")print(p1 is p2)p2.set_last_name("def")print(p1 is p2)if __name__ == '__main__':# test()# test1()test2()
输出
person initializing
True
True
输出一切都是我们所期望的,而且不管是类方法还是普通的函数,都可以使用lru_cache来实现对象方法的缓存,此外lru_cache还支持缓存大小限制(maxsize)和严格的参数类型匹配校验(typed)以及提供被装饰的对象方法以缓存的清除和信息查看等方法
@lru_cache
def function():print("call function")return 123def test3():# 只会调用一次functionf = function()f2 = function()# 缓存的参数信息print(function.cache_parameters())# 缓存信息print(function.cache_info())# 清除缓存function.cache_clear()print(function.cache_info())if __name__ == '__main__':test3()
输出
call function
CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
{'maxsize': 128, 'typed': False}
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)