把函數部分參數固定下來,相當於爲部分的參數添加了一個固定的默認值,
形成一個新的函數並返回
從partial生成的新函數,是對原函數的封裝
import functools
import inspect
def add(x, y, *args) -> int:
print(args)
return x + y
newadd = functools.partial(add, 1, 3, 6, 5)
print("a", newadd(7))
print(newadd(7, 10))
# print(newadd(9, 10, y=20, x=26)) 1,3已經位置參數對應給了x,y,而後面又關鍵字傳x,y
print(newadd())
print(inspect.signature(add))
print(inspect.signature(newadd))
out:
(6, 5, 7)
a 4
(6, 5, 7, 10)
4
(6, 5)
4
(x, y, *args) -> int
(*args) -> int
核心源碼:
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(args + fargs), **newkeywords) # 如果是newadd = functools.partial(add, 1),newadd(3) (1,)+(3,)=(1,3)
newfunc.func = func
newfunc.args = args
newfunc.keywords = keywords
return newfunc
@functools.lru_cache(maxsize=128, typed=False)
Least-recently-used裝飾器,lru,最近最少使用。
如果maxsize設置爲NONE,則禁用lru功能,並且緩存可以無限增長
當maxsize爲二的冪時,lru功能執行最好
如果typed設置爲True,則不同類型的函數參數將單獨緩存。
例如:f(3)和f(3.0)將被視爲具有不同結果的調用
#lru_cache 核心源碼
def _make_key(args, kwds, typed,
kwd_mark = (object(),),
fasttypes = {int, str, frozenset, type(None)},
sorted=sorted, tuple=tuple, type=type, len=len):
key = args
if kwds:
sorted_items = sorted(kwds.items())
key += kwd_mark
for item in sorted_items:
key += item
if typed:
key += tuple(type(v) for v in args)
if kwds:
key += tuple(type(v) for k, v in sorted_items)
elif len(key) == 1 and type(key[0]) in fasttypes:
return key[0]
return _HashedSeq(key)
class _HashedSeq(list):
__slots__ = 'hashvalue'
def __init__(self, tup, hash=hash):
self[:] = tup # lst = [] lst[:] = tup
self.hashvalue = hash(tup)
def __hash__(self):
return self.hashvalue
通過一個字典緩存被裝飾函數的調用和返回值
Key:
functools._make_key((4,6),{'z':3},False)
functools._make_key((4,6,3),{},False)
functools._make_key(tuple(),{'z':3,'x':4,'y':6},False)
functools._make_key(tuple(),{'z':3,'x':4,'y':6}, True)
#菲波拉契數列改造
import functools
@functools.lru_cache() # maxsize=None
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print([fib(x)for x in range(35)])
out:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887]
使用前提:
同樣的函數參數一定得到同樣的結果
函數執行時間很長,且要執行多次
本質是函數調用的參數--返回值
缺點:
不支持緩存過期,key無法過期,失效
不支持清除操作
不支持分佈式,是一個單機的緩存
適用場景:
單機上需要空間換時間的地方,可以用緩存來將計算變成快速查詢
練習:
1、實現一個cache裝飾器,實現可過期,可刪除功能,可以不換出
2、寫一個命令分發器
程序員可以方便的註冊函數到某一個命令,用戶輸出命令時,路由到註冊的函數
如果此命令沒有對應的註冊函數,執行默認函數
用戶輸入用input(">>")
第一題:
from functools import wraps
import inspect
import time
import datetime
def m_cache(duration): # 第一步,傳入5
def _cache(fn): # 第三步,傳入add
local_cache = {}
@wraps(fn) # add
def wrapper(*args, **kwargs):
# local_cache 有沒有過期的key
def clear_expire(cache):
expire_keys = []
for k, (_, timestamp) in cache.items():
if datetime.datetime.now().timestamp() - timestamp > duration: # 5
expire_keys.append(k)
for ekey in expire_keys:
cache.pop(ekey)
print(expire_keys)
clear_expire(local_cache)
def make_key():
key_dict = {} # sorted
sig = inspect.signature(fn)
params = sig.parameters # 有序字典
param_list = list(params.keys())
# 位置參數
for i, v in enumerate(args):
k = param_list[i]
key_dict[k] = v
# 關鍵字參數
key_dict.update(kwargs) # 直接合並
# 缺省值處理
for k in params.keys():
if k not in key_dict.keys():
key_dict[k] = params[k].default
return tuple(sorted(key_dict.items()))
key = make_key()
if key not in local_cache.keys():
ret = fn(*args, **kwargs)
local_cache[key] = (ret, datetime.datetime.now().timestamp()) # local_cache (k,(tuple))
return key, local_cache[key] # 第九步,此wrapper執行完成,返回值。
return wrapper # 第四步,返回wrapper
return _cache # 第二步,返回_cache函數
def logger(fn): # 第五步,傳入wrapper(第四步裏的wrapper)
@wraps(fn) # 第四步裏的wrapper
def wrapper(*args, **kwargs): # 第七步,當60行真正調用時,爲此wrapper(5, 8)
start = datetime.datetime.now()
ret = fn(*args, **kwargs) # 第八步,執行第四步裏的wrapper(5, 8)
delta = (datetime.datetime.now() - start).total_seconds() # 第十步,計算時間
print(delta)
return ret # 第十一步,返回ret的值
return wrapper # 第六步,最後返回wrapper 當72行真正調用時,爲此wrapper(5, 8)
@logger # logger(m_cache(add))
@m_cache(5) # m_cache()(add) 相當於_cache(add) # add() = logger(_cache(add))()
def add(x, y=5):
time.sleep(5)
ret = x + y
return ret
print(add(5, 8)) # add(5,8) = logger(_cache(add))(5, 8)
print(add(x=5, y=8))
time.sleep(3)
print(add(5, 8))
print(add(x=5, y=8))
out:
[]
5.006635
((('x', 5), ('y', 8)), (13, 1550406262.473862))
[]
0.00014
((('x', 5), ('y', 8)), (13, 1550406262.473862))
[]
0.00023
((('x', 5), ('y', 8)), (13, 1550406262.473862))
[]
0.000131
((('x', 5), ('y', 8)), (13, 1550406262.473862)) #由於 每次都是重新定義,所以沒有過期的key
print(add(x=5, y=8))
out:
[(('x', 5), ('y', 8))] # 等會單獨執行,就是有過期的緩存被清除了
5.008303
((('x', 5), ('y', 8)), (13, 1550406285.663602))
第二題:
# 命令分發器
def cmds_dispatcher():
commands = {}
def reg(name): # 註冊
def _reg(fn):
commands[name] = fn
return fn
return _reg
def defaultfun():
print('Unknown command')
def dispatcher():
while True:
cmd = input('>>')
if cmd.strip() == 'quit':
return
commands.get(cmd, defaultfun)()
return reg, dispatcher
reg, dispatcher = cmds_dispatcher()
# r, d = cmds_dispatcher()
@reg('ronn')
def foo1(): # 自定義函數
print('welcome to foo1')
@reg('tonn')
def foo2():
print('welcome to foo2')
dispatcher()
out:
>>hello
Unknown command
>>ronn
welcome to foo1
>>tonn
welcome to foo2
>>quit