实现一个cache 缓存,实现可过期被清除的功能
简化设计,函数的形参定义不包含可变位置参数、可变关键词参数和keyword-only参数
可以不考虑缓存大小,也不用考虑缓存满了之后的换出问题
编写的函数,满足:
def add(x=4, y=5):
time.sleep(3)
return x + y
以下6种,可以认为是同一种调用
print(1, add(4,5))
print(2, add(4))
print(3, add(y=5))
print(4, add(x=4,y=5))
print(5, add(y=5,x=4))
print(6, add())
分析:
数据类型的选择
缓存的应用场景,是有数据需要频繁查询,且每次查询都需要大量计算或者等待时间之后才能返回结果的情况,使
用缓存来提高查询速度,用内存空间换取查询、加载的时间。
cache应该选用什么数据结构?
便于查询的,且能快速获得数据的数据结构。
每次查询的时候,只要输入一致,就应该得到同样的结果(顺序也一致,例如减法函数,参数顺序不一致,结果不
一样)
基于上面的分析,此数据结构应该是字典。
通过一个key,对应一个value。
key是参数列表组成的结构,value是函数返回值。难点在于key如何处理。
key的存储
key必须是
hashable
。
key能接受到位置参数和关键字参数传参。
位置参数是被收集在一个tuple中的,本身就有顺序。
关键字参数被收集在一个字典中,本身无序,这会带来一个问题,传参的顺序未必是字典中保存的顺序。如何解
决?
- OrderedDict行吗?可以,它可以记录顺序。
- 不用OrderedDict行吗?可以,用一个tuple保存排过序的字典的item的kv对。
key的异同
什么才算是相同的key呢?
import time
import functools
@functools.lru_cache()
def add(x,y):
time.sleep(3)
return x+ys
定义一个加法函数,那么传参方式就应该有以下4种:
1. add(4, 5)
2. add(4, y=5)
3. add(y=5, x=4)
4. add(x=4, y=5)
上面4种,可以有下面几种理解:
第一种:1、2、3、4都不同。
第二种:3和4相同,1、2和3都不同。
第三种:1、2、3、4全部相同。
lru_cache实现了第一种,从源码中可以看出单独的处理了位置参数和关键字参数。
但是函数定义为def add(4, y=5),使用了默认值,如何理解add(4, 5)和add(4)是否一样呢?
如果认为一样,那么lru_cache无能为力。
就需要使用inspect来自己实现算法。
key的要求
key必须是hashable。
由于key是所有实参组合而成,而且最好要作为key的,key一定要可以hash,但是如果key有不可hash类型数据,
就无法完成。
lru_cache就不可以,我们也很难实现,所以不能在实参中出现不可hash类型。
key算法设计
inspect模块获取函数签名后,取parameters,这是一个有序字典,会保存所有参数的信息。
构建一个字典params_dict,按照位置顺序从args中依次对应参数名和传入的实参,组成kv对,存入params_dict
中。
kwargs所有值update到params_dict中。
如果使用了缺省值的参数,不会出现在实参params_dict中,会出现在签名的parameters中,缺省值也在函数定义
中。
调用的方式
普通的函数调用可以,但是过于明显,最好类似lru_cache的方式,让调用者无察觉的使用缓存。
构建装饰器函数。
代码实现:
首先我们使用python自带的lru_cache模块进行编写:
#系统自带的lru_cache 模块
import functools
import time
@functools.lru_cache()
def add(x=4,y=5):
time.sleep(2)
return x+y
print(1, add(4,5))
print(2, add(4))
print(3, add(y=5))
print(4, add(x=4,y=5))
print(5, add(y=5,x=4))
print(6, add())
其运行的结果如下图所示: