Memoization/Caching 带默认可选参数

Memoization/Caching with default optional arguments

我想制作一个 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}

对于记忆,您可以使用 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.getfullargspec(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() 对于问题中的整个调用集只调用一次。每次调用都是根据位置指定的所有三个参数进行的。

您可以使用 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}