使用默认可选参数进行记忆/缓存

时间:2016-04-21 13:56:18

标签: python caching memoization

我想制作一个记忆功能的python装饰器。例如,如果

@memoization_decorator    
def add(a, b, negative=False):
    print "Computing"
    return (a + b) * (1 if negative is False else -1)

add(1, 2)
add(1, b=2)
add(1, 2, negative=False)
add(1, b=2, negative=False)
add(a=1, b=2, negative=False)
add(a=1, b=2)

我希望输出为

Computing
3
3
3
3
3
3

并且在最后6行的任何排列下输出应该相同。

这相当于查找将等效*args, **kwargs**组发送到记忆缓存dict的唯一键的映射。以上示例的*args, **kwargs等于

(1, 2), {}
(1,), {'b': 2}
(1, 2), {'negative': False}
(1,), {'b': 2, 'negative': False}
(), {'a': 1, 'b': 2, 'negative': False}
(), {'a': 1, 'b': 2}

2 个答案:

答案 0 :(得分:4)

对于记忆,您可以使用functools.lru_cache()

编辑:您的用例的问题在于,如果它们指定参数的方式不同,则不会认为两个函数调用是相同的。为了解决这个问题,我们可以编写自己的装饰器,它位于lru_cache()之上,并将参数转换为单个规范形式:

from functools import lru_cache, wraps
import inspect

def canonicalize_args(f):
    """Wrapper for functools.lru_cache() to canonicalize default                                                          
    and keyword arguments so cache hits are maximized."""

    @wraps(f)
    def wrapper(*args, **kwargs):
        sig = inspect.getargspec(f.__wrapped__)

        # build newargs by filling in defaults, args, kwargs                                                            
        newargs = [None] * len(sig.args)
        newargs[-len(sig.defaults):] = sig.defaults
        newargs[:len(args)] = args
        for name, value in kwargs.items():
            newargs[sig.args.index(name)] = value

        return f(*newargs)

    return wrapper

@canonicalize_args
@lru_cache()
def add(a, b, negative=False):
    print("Computing")
    return (a + b) * (1 if negative is False else -1)

现在add()仅针对问题中的整个调用调用一次。每个调用都是使用位置指定的所有三个参数进行的。

答案 1 :(得分:1)

您可以使用inspect.getcallargs()获取函数的规范参数列表。将它包装在装饰器中不应该太难。

In [1]: def add(a, b, negative=False):
    ...:     print("Computing")
    ...:     return (a + b) * (1 if negative is False else -1)
    ...:
    ...:

In [2]: inspect.getcallargs(add, 1, 2)
Out[2]: {'a': 1, 'b': 2, 'negative': False}

In [3]: inspect.getcallargs(add, 1, 2, True)
Out[3]: {'a': 1, 'b': 2, 'negative': True}

In [4]: inspect.getcallargs(add, 1, 2, negative=False)
Out[4]: {'a': 1, 'b': 2, 'negative': False}

In [5]: inspect.getcallargs(add, 1, b=2, negative=False)
Out[5]: {'a': 1, 'b': 2, 'negative': False}

In [6]: inspect.getcallargs(add, 1, b=2)
Out[6]: {'a': 1, 'b': 2, 'negative': False}