经常在学习python递归的时候,大家总是可以看到lru_cache
装饰器,说这个装饰器可以减少重复函数的计算。
那么我们今天就来看看,这个函数的一些优缺点。帮助大家从全新的维度来理解一下。
一般来说,我们都是直接使用递归函数的。就像是下面这样的代码:计算斐波那契数列的代码。很常见。
def fib(n):
if n < 2:
return n
else:
return fib(n-1) + fib(n-2)
import timeit
t1 = timeit.Timer("fib(40)","from __main__ import fib")
print(t1.timeit(1))
上面的代码写好之后,运行需要26秒。(在我的计算机上,不同计算机不一样)
lru_cache
那么按照很多的python教程来说,他们就会让你使用lru_cache
装饰器,这样你就可以缩短时间。
确实,因为lru_cache
装饰器可以让保存重复计算的值,然后在需要的时候,拿出来,就不需要再去计算了。那么我们把代码改成这样的,然后看一下效果:
# 3. use lru_cache to reduce the recursion
from functools import lru_cache
import timeit
@lru_cache(maxsize=100)
def fib3(n):
if n <2:
return n
else:
return fib3(n-1) + fib3(n-2)
t1 = timeit.Timer("fib3(40)", "from __main__ import fib3")
print(t1.timeit(1))
看一下时间,确实是很少啦,从26秒降低到小数点后5位了。
通常来说,大部分python教程到这里就停止了。但是我们这里分析一下这么做的缺点。
就上面三个点,来展开聊了。
lru_cache
记录缓存数据的时候,是使用的字典形式。然后如果max_size
很大的时候,确实很占用很多内存。优化的方法没啥好的办法,不同的函数要特别注意。比如我们这里的斐波那契数列。其实优化成这样的代码,就算是不加lru_cache
装饰器,计算的也很快。代码如下:
# 2. improve the algo to cal fib vlaue
def fib2(n):
a, b, c = 0, 1, n
for i in range(n-1):
c = a+b
a, b = b, c
return c
t1 = timeit.Timer("fib2(40)", "from __main__ import fib2")
print(t1.timeit(1))
看看运行的时间,是小数点后6位了。比加lru_cache
的函数还要快!!!
lru_cache
不能装饰特殊的函数lru_cache
装饰器是不能直接 使用于那种返回容器类型的函数
,这里举个例子。
import numpy as np
@lru_cache
def cached_function(param):
print(f"running cached_function on: {param}")
return np.array([param])
for number in [100, 100, 100, 200]:
res = cached_function(number)
res *= 5
print(f"number: {number}, result: {res}")
在上面的代码中,对函数cached_function
使用了装饰器。然后使用for循环计算了4次100``一次200
。每次计算后,然后对结果乘以5。
那么按照道理来说,print(f"number: {number}, result: {res}")
打印出来的结果中:res
是number
的5倍。但是结果不是这样的。
查看上面的运行结果,你就知道了,虽然100只是计算了1次,但是结果是按照5倍不断的增长的。这错了哇。
其实就是解决浅拷贝问题。我们按照要求,对lru_cache
装饰器做一下改进。改进后的函数,就可以去装饰返回容器类型的函数
了。
from functools import lru_cache
from copy import deepcopy
def copying_lru_cache(maxsize=10, typed=False):
def decorator(f):
cached_func = lru_cache(maxsize=maxsize, typed=typed)(f)
def wrapper(*args, **kwargs):
return deepcopy(cached_func(*args, **kwargs))
return wrapper
return decorator
@copying_lru_cache()
def cached_function(param):
print(f"running cached_function on: {param}")
return np.array([param])
for number in [100, 100, 100, 200]:
res = cached_function(number)
res *= 5
print(f"number: {number}, result: {res}")
上面代码运行如下:
查看结果,虽然返回的是容器类型的,但是没啥问题了。
在最后,使用lru_cache
的时候,注意下面几点:
lru_cache
。https://stackoverflow.com/questions/54909357/how-to-get-functools-lru-cache-to-return-new-instances/54909677#54909677
https://twitter.com/lais_bsc/status/1524383940764647424