代码改变世界

lru_cahce装饰器

2019-08-20 10:34  美丽的名字  阅读(165)  评论(0)    收藏  举报

这个装饰器实现了备忘的功能,是一项优化技术,把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。lru 是(least recently used)的缩写,即最近最少使用原则。表明缓存不会无限制增长,一段时间不用的缓存条目会被扔掉。 
这个装饰器支持传入参数,还能有这种操作的?maxsize 是保存最近多少个调用的结果,最好设置为 2 的倍数,默认为 128。如果设置为 None 的话就相当于是 maxsize 为正无穷了。还有一个参数是 type,如果 type 设置为 true,即把不同参数类型得到的结果分开保存,如 f(3) 和 f(3.0) 会被区分开。 

 

下面通过一个简单的测试验证下使用lru_chache装饰器和不使用这个装饰器的区别:

 

先定义一个追踪函数:

def track(func):
    @functools.wraps(func)
    def inner(*args):
        result = func(*args)
        print("{} --> ({}) --> {} ".format(func.__name__, args[0], result))
        return result
    return inner

通常递归函数比较适合使用这个装饰器,就拿斐波那契数列测试:

不使用装饰器:

@track
def fib(n):
    if n < 2:
        return n
    return fib(n - 2) + fib(n - 1)

使用装饰器:

@functools.lru_cache()
@track
def fib_with_cache(n):
    if n < 2:
        return n
    return fib_with_cache(n - 2) + fib_with_cache(n - 1)

 

不使用装饰器的结果:

fib(10) 调用了 177 次, 共花费了 0.007 秒 

fib(10)

 759 function calls (407 primitive calls) in 0.007 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.007    0.007 {built-in method builtins.exec}
        1    0.000    0.000    0.007    0.007 decorator.py:1(<module>)
    177/1    0.000    0.000    0.007    0.007 decorator.py:5(inner)
    177/1    0.000    0.000    0.007    0.007 decorator.py:12(fib)
      177    0.006    0.000    0.006    0.000 {built-in method builtins.print}
      177    0.000    0.000    0.000    0.000 {method 'format' of 'str' objects}
        2    0.000    0.000    0.000    0.000 decorator.py:4(track)
        3    0.000    0.000    0.000    0.000 functools.py:43(update_wrapper)
        1    0.000    0.000    0.000    0.000 functools.py:422(decorating_function)
       21    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}
       15    0.000    0.000    0.000    0.000 {built-in method builtins.setattr}
        2    0.000    0.000    0.000    0.000 functools.py:73(wraps)
        3    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 functools.py:391(lru_cache)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

# 追踪结果
fib --> (0) --> 0 
fib --> (1) --> 1 
fib --> (2) --> 1 
fib --> (1) --> 1 
fib --> (0) --> 0 
fib --> (1) --> 1 
fib --> (2) --> 1 
fib --> (3) --> 2 
fib --> (4) --> 3 
fib --> (1) --> 1 
fib --> (0) --> 0 
fib --> (1) --> 1 
fib --> (2) --> 1 
fib --> (3) --> 2 
fib --> (0) --> 0 
fib --> (1) --> 1 
fib --> (2) --> 1 
fib --> (1) --> 1 
fib --> (0) --> 0 
fib --> (1) --> 1 
fib --> (2) --> 1 
fib --> (3) --> 2 
fib --> (4) --> 3 
fib --> (5) --> 5 
fib --> (6) --> 8 
fib --> (1) --> 1 
fib --> (0) --> 0 
fib --> (1) --> 1 
fib --> (2) --> 1 
往后的省略...

 

使用装饰器的结果;

可以很明显的看到,使用缓存的时候,只调用了 11 次就得出了结果,并且花费时间只为 0.002 秒

fib_with_cache(10)

     95 function calls (75 primitive calls) in 0.002 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.002    0.002 {built-in method builtins.exec}
        1    0.000    0.000    0.002    0.002 decorator.py:1(<module>)
     11/1    0.000    0.000    0.002    0.002 decorator.py:5(inner)
     11/1    0.000    0.000    0.002    0.002 decorator.py:18(fib_with_cache)
       11    0.002    0.000    0.002    0.000 {built-in method builtins.print}
        2    0.000    0.000    0.000    0.000 decorator.py:4(track)
        3    0.000    0.000    0.000    0.000 functools.py:43(update_wrapper)
       11    0.000    0.000    0.000    0.000 {method 'format' of 'str' objects}
        1    0.000    0.000    0.000    0.000 functools.py:422(decorating_function)
       21    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}
       15    0.000    0.000    0.000    0.000 {built-in method builtins.setattr}
        2    0.000    0.000    0.000    0.000 functools.py:73(wraps)
        3    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 functools.py:391(lru_cache)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

# 追踪结果
fib_with_cache --> (0) --> 0 
fib_with_cache --> (1) --> 1 
fib_with_cache --> (2) --> 1 
fib_with_cache --> (3) --> 2 
fib_with_cache --> (4) --> 3 
fib_with_cache --> (5) --> 5 
fib_with_cache --> (6) --> 8 
fib_with_cache --> (7) --> 13 
fib_with_cache --> (8) --> 21 
fib_with_cache --> (9) --> 34 
fib_with_cache --> (10) --> 55 

这个差距还不明显,我们再看下更多递归次数的情况:

调用了 4356617 次,花费 168.122 秒 

fib(31)  

17426519 function calls (8713287 primitive calls) in 168.122 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000  168.122  168.122 {built-in method builtins.exec}
        1    0.000    0.000  168.122  168.122 decorator.py:1(<module>)
4356617/1    8.046    0.000  168.122  168.122 decorator.py:5(inner)
4356617/1    4.250    0.000  168.122  168.122 decorator.py:12(fib)
  4356617  150.176    0.000  150.176    0.000 {built-in method builtins.print}
  4356617    5.650    0.000    5.650    0.000 {method 'format' of 'str' objects}
        2    0.000    0.000    0.000    0.000 decorator.py:4(track)
        3    0.000    0.000    0.000    0.000 functools.py:43(update_wrapper)
        1    0.000    0.000    0.000    0.000 functools.py:422(decorating_function)
       21    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}
       15    0.000    0.000    0.000    0.000 {built-in method builtins.setattr}
        2    0.000    0.000    0.000    0.000 functools.py:73(wraps)
        3    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 functools.py:391(lru_cache)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
fib_with_cache(31)  
179 function calls (117 primitive calls) in 0.003 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.003    0.003 {built-in method builtins.exec}
        1    0.000    0.000    0.003    0.003 decorator.py:1(<module>)
     32/1    0.000    0.000    0.003    0.003 decorator.py:5(inner)
     32/1    0.000    0.000    0.003    0.003 decorator.py:18(fib_with_cache)
       32    0.002    0.000    0.002    0.000 {built-in method builtins.print}
       32    0.000    0.000    0.000    0.000 {method 'format' of 'str' objects}
        2    0.000    0.000    0.000    0.000 decorator.py:4(track)
        3    0.000    0.000    0.000    0.000 functools.py:43(update_wrapper)
        1    0.000    0.000    0.000    0.000 functools.py:422(decorating_function)
       21    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}
       15    0.000    0.000    0.000    0.000 {built-in method builtins.setattr}
        2    0.000    0.000    0.000    0.000 functools.py:73(wraps)
        3    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 functools.py:391(lru_cache)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

而使用缓存装饰器的情况下只调用了32次,花费0.003秒


 

这个装饰器还提供 cache_clear() 用于清理缓存,以及 cache_info() 用于查看缓存信息,其他具体信息可以参考官网介绍:https://docs.python.org/3/library/functools.html