Python的lru_cache装饰器,装饰器,可变参数*args

参考链接

今天写算法题dp的自上而下的算法的时候,看到有人用了``@functools.lru_cache这个函数。最神奇的是没用之前我的算法是超时的,用了之后居然就跑出来了

这个函数是什么?

根据官方文档,这个函数的定义如下:

1
@functools.lru_cache(maxsize=128, typed=False)

1
2
3
4
5
6
7
8
9
10
11
一个为函数提供缓存功能的装饰器,缓存 maxsize 组传入参数,在下次以相同参数调用时直接返回上一次的结果。用以节约高开销或I/O函数的调用时间。

由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的。

不同模式的参数可能被视为不同从而产生多个缓存项,例如, f(a=1, b=2) 和 f(b=2, a=1) 因其参数顺序不同,可能会被缓存两次。

如果指定了 user_function,它必须是一个可调用对象。 这允许 lru_cache 装饰器被直接应用于一个用户自定义函数,让 maxsize 保持其默认值 128:

如果 maxsize 设置为 None ,LRU功能将被禁用且缓存数量无上限。 maxsize 设置为2的幂时可获得最佳性能。

如果 typed 设置为true,不同类型的函数参数将被分别缓存。例如, f(3) 和 f(3.0) 将被视为不同而分别缓存。
  • 取这个函数名的意义在于,LRU叫做最久未使用算法,是一种缓存文件的置换方法。这些种方法会使用最久没有被访问的对象作为置换进去的对象。于此相对的方法还有,最近最少使用,非最近使用,先进先出等等算法。
  • 也就是说,这个函数默认会储存128个调用结果。这个函数实现了备忘功能,会避免在传入相同函数的重复计算。超过这个条目的缓存,会根据LRU规则被丢弃掉。可以使cache_clear()来清除缓存

实现场景

  • 比如在一些使用recursion的场景下,会重复调用很多相同参数的函数(我的雅虎网测也是因此没有通过)。使用这种方法可以很好的减少计算量
  • 比如斐波那契的计算,计算31的时候调用函数的次数已经差出了几千倍,时间也差很多
  • 使用的时候记得import functools
    1
    2
    3
    4
    5
    @functools.lru_cache(None)
    def fib_with_cache(n):
    if n < 2:
    return n
    return fib_with_cache(n - 2) + fib_with_cache(n - 1)

什么是装饰器

既然说到了这种装饰器,我就突然发现自己还不知道什么是装饰器。

参考链接

核心思想:python万物皆对象,包括函数本身

装饰器(decorators)

  • 整体来说,装饰器的作用就是在不影响以前的函数的情况下,提供我们需要的效果。本质上来说,装饰器也是一个python的函数,他可以让其他函数实现额外的功能,而不修改本身的代码。可以用装饰器实现大量复用的功能
  • 装饰器返回的也是函数对象。

一个例子

如果现在有一个函数

1
2
def foo():
print('i am foo')

但是我想要一个需求,就是记录下来执行日志

1
2
3
def foo():
print('i am foo')
logging.info("foo is running")

但是如果在很多个函数中都想要实现这个功能,那么我们需要修改每个函数的内容,加上log的代码。如果这个log的功能只是测试的时候用一下,那么修改的时候还需要把所有的代码再删掉。这时候最开始的想法是写一个新的函数包括这些功能,比如

1
2
3
4
5
6
7
8
def foo():
print("THIS is foo")

def decotator(func):
print("start...%s"%func)
func()

decotator(foo)

但是写成这样挺丑的,而且调用这个函数的时候就变成了不是调用这个函数本身,这时候就需要装饰器来工作。

  • 面函数里面的wrapper是一个函数对象,因为修饰器的参数是函数,wrapper是来解决这个函数还有参数的问题。
  • 这个效果也就等同于foo = Log(foo),然后调用的时候调用foo(arg)的效果。把这个方法自动化之后就是加上@让他自动变成装饰器了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def Log(func):
    def wrapper(*args, **kwargs):
    logging.warn("%s is running"%func.__name__)
    return func(*args)
    return wrapper

    @Log
    def foo():
    print("this is foo")
  • 在装饰器调用的时候,用@来表示,避免再一次的赋值操作(本来应该是 foo = Log(foo) foo()的这种调用方法。
    在实现带参数的装饰器的时候,可以给装饰器再套一层。注意函数名带括号和不带括号的区别

    1
    2
    3
    4
    5
    6
    7
    8
    def Log(level):
    def decorator(func):
    def wrapper(*args,**kwargs):
    if level == "1":
    logging.warn("start %s"%func.__name__)
    return func(*args)
    return wrapper
    return decorator

其他的一些

  • 类装饰器:如果装饰器的参数是个类,可以给类写装饰器,可以实现更多的功能
  • 装饰器的顺序,离这个函数越近的越是

可变参数

参考
上面的参数里面有加了星标的,这个突然发现自己有一点理解不准确,而且这么久了感觉也没怎么用到过。就一起写在这里了
总得来说,可变参数就是可以处理不同数量的参数

*args

1
2
3
4
5
6
7
def argsFunc(a, *args):
print a
print args

>>> argsFunc(1, 2, 3, 4)
1
(2, 3, 4)

**kwargs

  • 参数名字前面加两个星号,表示参数会被存放在dict里面,这时候调用这个函数的时候需要输入arg1 = value1, arg2 = value2这种形式
  • 这种的一般会在调用sql的时候用到