一、寫一個裝飾器,實現緩存功能,容許過時,但沒有換出,沒有清除
緩存
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:面向方面
針對一類問題作處理,與具體實現無關
裝飾器常見的使用場景:
監控、緩存、路由、權限、參數檢查