lru_cache的增强实现
from functools import wraps
import time,inspect,datetime
def m_cache(duration):
def _cache(fn):
local_cache={}
@wraps(fn)
def wrapper(*args,**kwargs):
def clear_expire_keys(cache):
expire_keys=[] # 记录过期key
for k,(_,timestamp) in cache.items(): # 寻找过期key
if datetime.datetime.now().timestamp()-timestamp > duration:
expire_keys.append(k)
for k in expire_keys: # 清除过期key
local_cache.pop(k)
clear_expire_keys(local_cache)
def make_key():
key_dict={}
sig=inspect.signature(fn)
od=sig.parameters
# param_list=[key for key in od.keys()]
param_list=list(od.keys()) # inspect获取的参数列表
# positional arguments
for index,v in enumerate(args):
# print(index,v)
k=param_list[index]
key_dict[k]=v
# keywords arguments
for k,v in kwargs.items():
key_dict[k]=v
# key_dict.update(kwargs)
for k in od.keys(): # 对缺省参数的处理,置于key生成前
if k not in key_dict.keys():
key_dict[k]=od[k].default
return tuple(sorted(key_dict.items()))
key=make_key()
print(key)
if key not in local_cache.keys():
ret=fn(*args,**kwargs)
local_cache[key]=ret,datetime.datetime.now().timestamp()
return local_cache[key][0]
return wrapper
return _cache
def logger(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
start=datetime.datetime.now()
ret=fn(*args,**kwargs)
delta=(datetime.datetime.now()-start).total_seconds()
print(delta)
return ret
return wrapper
@logger
@m_cache(3)
def dix(x,y=6,z=3):
time.sleep(z)
print('x: {}\ty: {}'.format(x,y))
print('\n'*3)
return x+y
print(dix(7))
print(dix(7,y=6))
print(dix(x=7,y=6))
print(dix(y=6,x=7))
print(dix(4,8,y=6,x=7)) # 不符合原函数的定义,但是形成key一样,直接从local_cache取结果,避免了函数的的真正执行,故未报错
time.sleep(4)
print(dix(7))
print(dix(7,y=6))
print(dix(x=7,y=6))
print(dix(y=6,x=7))
print(dix(4,8,y=6,x=7))
缓存过期:
是dict中某个key过期,可以对每一个key单独设置过期时间,也可以对key设定统一过期时间
上面的装饰器增加一个参数,形成带参装饰器
@m_cache(3)代表key生存3s后过期
带参装饰器等于在原来的装饰器外面再嵌套一层
清除时机:
- 用到某个key之前,先判断是否过期,如果过期,重新调用函数生成新的key对应的value
- 一个线程负责清除过期key,本次在创建key之前,清除所有过期key
value的设计:
- key => (v,createtimestamp)
- key => (v,createtimestamp,duration)
duration是过期时间,这样每一个key就可以单独控制过期时间,这种设计中,-1可以表示永不过期,0可以表示立即过期
import datetime import functools, copy import inspect, time def lru_cache(timeout: int): def pack(fn): local_cache = {} @functools.wraps(fn) def wrapper(*args, **kwargs): def clear_expires(): for k, (_, ts) in tuple(local_cache.items()): if time.time() - ts > timeout: local_cache.pop(k) clear_expires() signature = inspect.signature(fn) parameters = signature.parameters key_dict = dict(parameters) # 形成新dict # print(key_dict, type(key_dict)) keys = tuple(parameters.keys()) # 判断是否有*args(VAR_POSITIONAL) & **kwargs(VAR_KEYWORD)类型参数 positional_flag = False positional_name = None positional_tuple = () keyword_flag = False # 假定不存在VAR_KEYWORD keyword_name = None # VAR_KEYWORD 变量名 keyword_dict = {} # 存在VAR_KEYWORD, 直接初始化为dict() for parameter_name, parameter in parameters.items(): # dict有序可取keys最后一个元素, 直接进行判断 if str(parameter.kind) == 'VAR_POSITIONAL': positional_flag = True positional_name = parameter_name # VAR_POSITIONAL参数名 key_dict.pop(positional_name) # 从key_dict移除 if str(parameter.kind) == 'VAR_KEYWORD': keyword_flag = True keyword_name = parameter_name # VAR_KEYWORD参数名 key_dict.pop(keyword_name) # 从key_dict移除 # positional for i, parameter_value in enumerate(args): parameter_name = keys[i] parameter = parameters[parameter_name] if str(parameter.kind) in ('POSITIONAL_ONLY', 'POSITIONAL_OR_KEYWORD'): key_dict[parameter_name] = parameter_value elif str(parameter.kind) == 'VAR_POSITIONAL': positional_tuple = args[i:] # 收集所有VAR_POSITIONAL参数, 并退出循环 break # keyword for parameter_name, parameter_value in kwargs.items(): if parameter_name in key_dict: # 关键字传参为预定义参数 POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD, KEYWORD_ONLY parameter = parameters[parameter_name] # 获取inspect.Parameter对象 if str(parameter.kind) == 'POSITIONAL_ONLY': raise TypeError(f'{fn.__name__}() got some positional-only arguments passwd as keyword arguments: {repr(parameter_name)}') else: # 移除了 VAR_POSITIONAL & VAR_KEYWORD, 此分支只能是 POSITIONAL_OR_KEYWORD or KEYWORD_ONLY if isinstance(key_dict[parameter_name], inspect.Parameter): # 还未被赋值 key_dict[parameter_name] = parameter_value else: # 重复赋值 raise TypeError(f'{fn.__name__}() got multiple values for argument {repr(parameter_name)}') else: if not keyword_flag: # 不存在VAR_KEYWORD raise TypeError(f'{fn.__name__} got an unexpected keyword_name argument {repr(parameter_name)}') else: # 存在VAR_KEYWORD keyword_dict.setdefault(parameter_name, parameter_value) # 附加VAR_POSITIONAL & VAR_KEYWORD if positional_flag: key_dict[positional_name] = positional_tuple if keyword_flag: key_dict[keyword_name] = keyword_dict # 处理默认值参数 for parameter_name, parameter_value in key_dict.items(): if isinstance(parameter_value, inspect.Parameter): # 默认值参数还未被处理 key_dict[parameter_name] = parameter_value.default print('{0}key_dict: {key_dict} {0}'.format('~' * 20, key_dict=key_dict)) def make_key(d: dict, flag, keyword): if flag: tmp = d.pop(keyword) return tuple(sorted(d.items())) + tuple(sorted(tmp.items())) return tuple(sorted(d.items())) key = make_key(key_dict, keyword_flag, keyword_name) print('{0}key: {key} {0}'.format('~' * 20, key=key)) if key not in local_cache: local_cache[key] = fn(*args, **kwargs), time.time() return local_cache[key][0] return wrapper return pack def logger(fn): def wrapper(*args, **kwargs): commence = datetime.datetime.now() ret = fn(*args, **kwargs) delta = (datetime.datetime.now() - commence).total_seconds() print(f'{fn.__name__} took {delta}') return ret return wrapper @logger @lru_cache(timeout=3) def vet(x, /, y: int = 7, *args, z=10, t: int, **kwargs) -> int: time.sleep(2.5) # print(f'x: {x}, y: {y}, args: {args}, z: {z}, t: {t}, kwargs: {kwargs}') ret = x + y + z + t for arg in args: ret += arg for v in kwargs.values(): ret += v return ret # print(vet(555, 22, 33, 44, 55, t=666, m=77)) # print(vet(555, 22, 33, 44, 55, t=666, m=77)) # vet(5, t=666, m=88) print(vet(5, t=6, m=7)) time.sleep(2.5) print(vet(5, 7, t=6, m=7))