python-使用缓存的方式-优化函数与lru_cache


递归函数的弊端

递归函数虽然编写时,使用很少的代码完成了庞大的功能,弊端非常明显--时间和空间的消耗。

eg:

import time


def fibonacci(n):
    if n < 2:
        return 1
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    
if __name__ == '__main__':
    t1 = time.time()
    print(fibonacci(35))
    t2 = time.time()
    print(f"cost: {t2 - t1} s")

结果:

14930352
cost: 5.771588563919067 s

耗时大概在5s,当数据继续增大时,消耗的是时间将会继续增加,越来越大。因为

这个递归函数的复杂性是O(2^n)

递归原理图:
当n=5时,斐波拉切数列的原理图如下:

根据原理图可知,其中大部分的数字是重复的,也就是说执行了很多重复的函数。

优化:将计算过的数值,缓存起来,然后再次进行计算。

用缓存优化递归

eg:

定义一个递归装饰器来做函数的缓存。

import time


def cache_decorator(func):
    cache_dict = {}
    
    def decorator(arg):
        try:
            return cache_dict[arg]
        except KeyError:
            return cache_dict.setdefault(arg, func(arg))
    
    return decorator

@cache_decorator
def fibonacci(n):
    if n < 2:
        return 1
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    
if __name__ == '__main__':
    t1 = time.time()
    print(fibonacci(35))
    t2 = time.time()
    print(f"cost: {t2 - t1} s")

结果:
14930352
cost: 0.0 s

使用缓存装饰器之后,函数执行时间,大幅度缩短。

虚线所指的节点则不需要重新计算,意味复杂度从O(2**n)降到了O(n)。

lru_cache 装饰器

上述优化器,不适用其他的函数,使用python标准库提供的装饰器来进行缓存(functools模块中lru_cache).

其是通过lru算法来进行缓存内容的淘汰。
参数:
maxsize: 设置缓存内存上限, 其值应当设置为2的n次幂, 值为None表示,没有上限。

typed: 表示不同参数类型,是否分别缓存, True:分别缓存

import time
from functools import lru_cache

def cache_decorator(func):
    cache_dict = {}
    
    def decorator(arg):
        try:
            return cache_dict[arg]
        except KeyError:
            return cache_dict.setdefault(arg, func(arg))
    
    return decorator

# @cache_decorator
@lru_cache(2**10, False)
def fibonacci(n):
    if n < 2:
        return 1
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    
if __name__ == '__main__':
    t1 = time.time()
    print(fibonacci(300))
    t2 = time.time()
    print(f"cost: {t2 - t1} s")

结果:
359579325206583560961765665172189099052367214309267232255589801
cost: 0.0009965896606445312 s