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后过期

带参装饰器等于在原来的装饰器外面再嵌套一层

 

清除时机:

  1. 用到某个key之前,先判断是否过期,如果过期,重新调用函数生成新的key对应的value
  2. 一个线程负责清除过期key,本次在创建key之前,清除所有过期key

value的设计:

  1. key => (v,createtimestamp)
  2. 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))

 

posted @ 2020-09-12 17:27  ascertain  阅读(134)  评论(0编辑  收藏  举报