【函数】0六、装饰器的应用

一、写一个装饰器,实现缓存功能,容许过时,但没有换出,没有清除
缓存

 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:面向方面

           针对一类问题作处理,与具体实现无关


装饰器常见的使用场景:

     监控、缓存、路由、权限、参数检查

相关文章
相关标签/搜索