Python 带有默认可选参数的备忘录/缓存

Python 带有默认可选参数的备忘录/缓存,python,caching,memoization,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

我想制作一个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
。上述示例中,**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}

对于您可以使用的备忘录

<> > >编辑:您的用例的问题是,如果它们指定参数的方式不同,则不考虑两个函数调用是相同的。为了解决这个问题,我们可以编写我们自己的decorator,它位于
lru\u 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()
只调用一次。每次调用都是使用所有三个按位置指定的参数进行的。

您可以使用来获取函数的规范参数列表。用一个装饰器包装它应该不会太难

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}

我试了你的建议,但不起作用。例如,连续执行
add(1,2)
add(1,2,negative=False)
两次执行
Computing\\3
,因此第二次调用是计算的,而不是从缓存返回的。@JonWarneke:我明白你的意思了。我已经扩展了我的答案,以包括完整的解决方案。对于除了常规参数之外还接受
**kwargs
的函数,这似乎是不正确的。
inspect.getargspec
从3.0版开始就被弃用:对于更新的API,使用
getfullargspec()
,它通常是一个替代品,但也能正确处理函数注释和仅关键字参数。自3.5版以来已弃用:改用
Signature.bind()
Signature.bind\u partial()