一、写一个装饰器,实现缓存功能,容许过时,但没有换出,没有清除
缓存
1)cache的必要元素:key --> valuebash
这里的key是函数的参数,value是函数的返回值app
2)超时时间
ide
超时时间如何存储函数
步骤1:
spa
In [28]: from functools import wraps In [29]: def cache(fn): ...: cache_dict = {} ...: @wraps ...: def wrap(*args, **kwargs): ...: # 如何拼装key ...: if key in cache_dict.keys(): ...: # 如何实现超时检测 ...: return cache_dict[key] ...: result = fn(*args, **kwargs) ...: cache_dict[key] = result ...: return result ...: reutrn wrap
如何拼装key?orm
参数名 + 参数值
路由
这里须要用到inspect库it
标准库inspect的用法:io
In [1]: import inspect In [2]: def add(x, y): ...: return x + Y ...: In [3]: inspect.signature Out[3]: <function inspect.signature> In [4]: inspect.signature(add) Out[4]: <Signature (x, y)> In [5]: sig = inspect.signature(add) # 获取到函数的签名 In [17]: sig.parameters Out[17]: mappingproxy({'x': <Parameter "x">, 'y': <Parameter "y">}) In [18]: for k in sig.parameters.keys(): # 获取参数 ...: print(k) ...: x y
步骤2:
拼接key
In [30]: from functools import wraps In [31]: import inspect In [32]: def cache(fn): ...: cache_dict = {} ...: @wraps ...: def wrap(*args, **kwargs): ...: key = [] ...: params = inspect.signature(fn).parameters ...: for i, arg in enumerate(args): ...: # 位置参数的名(行参x或y)和其值(实参) ...: name = list(params.keys())[i] ...: key.append((name, arg)) ...: key.extend(kwargs.items()) ...: # 拼接KEY ...: key.sort(key=lambda x: x[0] ...: key = '&'.join(['{}={}'.format(name, arg) for name, arg in key]) ...: ...: if key in cache_dict.keys(): ...: # 如何实现超时检测 ...: return cache_dict[key] ...: result = fn(*args, **kwargs) ...: cache_dict[key] = result ...: return result ...: reutrn wrap
步骤3:
当被装饰的函数有默认参数时,形成key不同,不走cache,怎么处理?
## inspect标准库的应用
# 当被装饰的参数有默认参数时 In [14]: def add(x, y=2): ...: return x + y ...: In [15]: sig = inspect.signature(add) In [16]: sig Out[16]: <Signature (x, y=2)> In [19]: params = sig.parameters In [20]: params Out[20]: mappingproxy({'x': <Parameter "x">, 'y': <Parameter "y=2">}) In [24]: for k,v in params.items(): ...: print(k, v) ...: ...: x x y y=2 In [25]: for k,v in params.items(): ...: print(k, v.default) ...: ...: ...: x <class 'inspect._empty'> # v.default y 2
处理默认参数:
In [48]: def cache(fn): ...: cache_dict = {} ...: @wraps(fn) ...: def wrap(*args, **kwargs): ...: key = [] ...: names = set() ...: params = inspect.signature(fn).parameters ...: for i, arg in enumerate(args): ...: # ...: name = list(params.keys())[i] ...: key.append((name, arg)) ...: names.add(name) ...: key.extend(kwargs.items()) ...: names.update(kwargs.keys()) ...: for k, v in params.items(): ...: if k not in names: ...: key.append((k, v.default)) ...: # 拼接KEY ...: key.sort(key=lambda x: x[0]) ...: key = '&'.join(['{}={}'.format(name, arg) for name, arg in key]) ...: print(key) ...: if key in cache_dict.keys(): ...: # 如何实现超时检测 ...: return cache_dict[key] ...: result = fn(*args, **kwargs) ...: cache_dict[key] = result ...: return result ...: return wrap ...: In [49]: @cache ...: def add(x, y=3): ...: return x + y ...: In [50]: add(1, 3) x=1&y=3 Out[50]: 4 In [51]: add(1) x=1&y=3 Out[51]: 4 In [52]: add(x=1, y=3) x=1&y=3 Out[52]: 4 In [53]: add(y=3, x=1) x=1&y=3 Out[53]: 4
步骤4:
实现过时功能
应该保存第一次传入该key和生成其值的时间
过时时间也应该由用户传入,那应该在哪里传入呢?
能够使用在装饰参数传入
若是在被装饰的参数传入,就比较麻烦了
In [61]: def cache(exp=0): ...: def _cache(fn): ...: cache_dict = {} ...: @wraps(fn) ...: def wrap(*args, **kwargs): ...: key = [] ...: names = set() ...: params = inspect.signature(fn).parameters ...: for i, arg in enumerate(args): ...: # ...: name = list(params.keys())[i] ...: key.append((name, arg)) ...: names.add(name) ...: key.extend(kwargs.items()) ...: names.update(kwargs.keys()) ...: for k, v in params.items(): ...: if k not in names: ...: key.append((k, v.default)) ...: # 拼接KEY ...: key.sort(key=lambda x: x[0]) ...: key = '&'.join(['{}={}'.format(name, arg) for name, arg in key]) ...: print(key) ...: if key in cache_dict.keys(): ...: result, timestamp = cache_dict[key] ...: if exp == 0 or datetime.datetime.now().timestamp() - timestamp < exp: ...: print("cache hit") ...: return cache_dict[key] ...: result = fn(*args, **kwargs) ...: cache_dict[key] = (result, datetime.datetime.now().timestamp()) ...: print("cache miss") ...: return result ...: return wrap ...: return _cache ...: In [68]: add(2, 3) # 第一次执行没有命中 x=2&y=3 cache miss Out[68]: 5 In [69]: add(2) # 5s内再次执行命中 x=2&y=3 cache hit Out[69]: (5, 1497970027.772658) In [70]: add(2, 3) # 过5s后再执行没有命中 x=2&y=3 cache miss Out[70]: 5
可能会有人担忧没有区分函数,在装饰不一样的函数,参数同样时,会混:
In [71]: @cache(500) ...: def add(x, y=3): ...: return x + y ...: In [72]: add(1, 3) x=1&y=3 cache miss Out[72]: 4 In [73]: add(1, 3) x=1&y=3 cache hit Out[73]: (4, 1497970231.435554) In [74]: add(1, 3) x=1&y=3 cache hit Out[74]: (4, 1497970231.435554) In [76]: @cache(500) ...: def xxj(x, y=3): ...: return x + y ...: In [77]: xxj(1) x=1&y=3 cache miss Out[77]: 4
并无混,为何?
二、装饰器的用途
AOP:面向方面
针对一类问题作处理,与具体实现无关
装饰器常见的使用场景:
监控、缓存、路由、权限、参数检查