cache
递归是一种十分方便的编程方法,但便捷带来的是内存爆炸的风险。
比如搞一个斐波那契数列并计时,有关time的解析请看这里:一文撸懂python内置的时间模块。
>>> fib = lambda n : n if n<2 else fib(n-1)+fib(n-2)
>>> def test(n):
... t = time.time()
... fib(n)
... print(time.time()-t)
...
...
>>> test(25)
0.025918960571289062
>>> test(30)
0.38434386253356934
>>> test(35)
3.017500877380371
>>> test(40)
34.15391778945923
这甚至还没我笔算快。
原因很简单,每次递归都得调用两次自己,也就是说,调用函数的次数呈指数增长——这要是不爆炸就有鬼了。
笔算之所以快,是因为把底层的计算结果存留在了纸上。cache
的用途与此相似,是把未命名函数缓存下来,这样就不必没完没了地重复计算。
import functools
@functools.cache
def fib(n):
return fib(n-1)+fib(n-2) if n > 1 else n
fib(40)
# 102334155 这个结果很快就得出了
def test(n):
t = time.time()
fib(n)
print(time.time()-t)
test(40)
#0.0
test(400)
#0.0
函数修饰器
@
是一种语法糖,名为函数修饰器,在上文中,通过cache
来修饰fib
,则每次调用fib
时,实则把fib
放到cache
中执行。或者可以把fib
理解为cache
的入口函数——每次调用fib
就相当于进入了cache
。
为了深入理解这种关系,咱们可以把test
改成一个修饰器。
def getTime(func):
def wrapper(*args,**kwargs):
t = time.time()
func(*args,**kwargs)
print(time.time()-t)
return wrapper
@getTime
def Fib(n):
print(fib(n))
Fib(55)
#139583862445 #执行Fib中print的结果
#0.0 #getTime中print的结果
wraps
使用修饰器的一个尴尬的结果是,我们要用的函数成为了一个入口,而真正执行的函数是修饰器返回的那个,二者之间的名字是不同的。
>>> Fib.__name__
'wrapper' #这个wrapper是getTime返回的函数的名字
update_wrapper
函数则可以更新函数的各种信息,而wraps
为其装饰器模式。
def getTime(func):
@functools.wraps(func)
def wrapper(*args,**kw):
t = time.time()
func(*args,**kw)
print(time.time()-t)
return wrapper
@getTime
def Fib(n):
print(fib(n))
print(Fib.__name__)
# 'Fib'
lru_cache
查看源码会发现,cache
的实现方法很简单,只是调用了lru_cache
。
def cache(user_function, /):
return lru_cache(maxsize=None)(user_function)
相对于cache,lru_cache提供了两个参数,一个是maxsize
,代表最大缓存,默认为128;typed
则是类型开关,为True
时,不同类型的数据将分别缓存。
singledispatch
由于Python中不支持函数重载,所以如果我想print一些东西的话大致就得这样写
def printSth(sth):
if type(sth)==int:
print(sth)
elif type(sth)==list:
for i in sth : print(i)
singledispatch则为这种泛型提供了一种类似重载的写法:
@functools.singledispatch
def printSth(sth):
pass
@printSth.register
def _(sth:int):
print(sth)
@printSth.register
def _(sth:list):
for i in sth : print(i)
其中register
是singledispatch
中定义的函数,其第一个参数是一种类型,若想实现上述功能,也可以采用另一种写法
@printSth.register(dict)
def _(sth):
for key in sth : print(key,sth[key])
调用一下
>>> printSth({"a":1,"b":2,"c":3})
a 1
b 2
c 3
reduce
除了一系列修饰器之外,functools
模块还提供了两个用起来很方便的函数。
functools.reduce(func,lst)
,是对lst中的元素,从左到右依次调用func
函数,其中ffunc
必须有两个参数。
>>> def add(x,y) : return x+y
...
>>> functools.reduce(add,[1,2,3,4])
10