【函數】06、裝飾器的應用

1、寫一個裝飾器,實現緩存功能,允許過期,但沒有換出,沒有清除

 1)cache的必要元素:key --> value

     這裏的key是函數的參數,value是函數的返回值


 2)超時時間

    超時時間如何存儲


步驟1:

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?

    參數名 + 參數值

    這裏需要用到inspect庫


標準庫inspect的用法:

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

  並沒有混,爲什麼?



2、裝飾器的用途

AOP:面向方面

           針對一類問題做處理,與具體實現無關


裝飾器常見的使用場景:

     監控、緩存、路由、權限、參數檢查



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章