把函数部分参数固定下来,相当于为部分的参数添加了一个固定的默认值,
形成一个新的函数并返回
从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