简化设计, 函数的形参定义不包含可变位置参数, 可变关键字参数和keyword-only参数, 可以暂时不考虑缓存大小, 也不考虑存满之后的换出问题
# 先来看这段代码
import time
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 能够接受到位置参数和关键字参数
-
位置参数收集到一个元组中, 本身就有顺序
-
关键字参数收集到字典中, 但字典本内元素无序, 传参顺序就会受到影响 …
- OrderDict 可以记录顺序
- 如果不用OrderDict 呢 ? 用一个元组排过序的字典item 的k, v对
key 的异同
什么才算相同的key 呢 ?
import time
import functools
@functools.lru_cache()
def add(x, y):
time.sleep(3)
return x+y
定义一个加法函数, 那么传参方式就应该有以下4种 :
- add(4, 5)
- add(4, y=5)
- add(y=5, x=4)
- add(x=4, y=5)
对于上面4种传参方式,可以有下面几种理解 :
- 上面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 是所有实参组合而成而且最好要作为key 的, key一定的可hash, 如果key 有不可hash类型就无法完成. lru_cache就不可以, 我们也无法实现, 所以不能在实参中出现不可hash 类型
def add1(x, y):
return y
输出结果 :
>> add1([], 5)
5
from functools import lru_cache
import time
@lru_cache()
def add(x, y=5):
time.sleep(3)
return y
输出结果 :
>> add(4)
5
>> add([], 5)
Traceback (most recent call last):
File "C:/**..**", line 1, in <module>
add([], 5)
TypeError: unhashable type: 'list'
缓存必须使用key, 但是key 必须可hash, 所以只能使用可以hash 的实参的函数调用
key 算法设计
- inspect 模块获取函数签名后, 取parameters, 这是一个有序字典, 会保存所有参数信息
- 构建一个字典params_dict, 按照位置顺序从args 中依次对应参数名和传入的实参, 组成kv 对, 存入params_dict 中
- kwargs 所有值update 到params_dict 中
- 如果使用了缺省值参数, 不会出现在实参params_dict 中, 缺省值也在函数定义中
调用方式
- 普通的函数调用可以, 但是对于明显, 最好类似lru_cache, 让调用者无察觉的使用缓存
代码模块如下 :
from functools import wraps
import inspect
def mag_cache(fn):
local_cache ={
} # 对不同函数名是不同cache
@wraps(fn)
def wrapper(*args, **kwargs): # 接收各种参数
# 构建key
ret = fn(*args, **kwargs)
return ret
return wrapper
@mag_cache
def add(x, y, z=6):
return x+y+z
目标 :
def add(x, y, z=6):
return x+y+z
add(4, 5)
add(4, 5, 6)
add(4, z=6, y=5)
add(4, y=6, z=6)
add(x=4, y=5, z=6)
add(z=6, x=4, y=5)
- 上面几种都等价, key 都是一样的, 这样都可以缓存
代码实现
def mag_cache(fn):
local_cache ={
} # 对不同函数名是不同cache
@wraps(fn)
def wrapper(*args, **kwargs):
# 构建key
sig = inspect.signature