带有默认可选参数的记忆/缓存

Jon*_*eke 3 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)
Run Code Online (Sandbox Code Playgroud)

我希望输出是

Computing
3
3
3
3
3
3
Run Code Online (Sandbox Code Playgroud)

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

这相当于找到一个映射,将等效的集合发送*args, **kwargs**到 memoization 缓存的唯一键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}
Run Code Online (Sandbox Code Playgroud)

Joh*_*nck 5

对于记忆,您可以使用 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)
Run Code Online (Sandbox Code Playgroud)

Nowadd()对于问题中的整个调用集只调用一次。每个调用都使用按位置指定的所有三个参数进行。

  • `inspect.getargspec` 自版本 3.0 起已被弃用:使用 `getfullargspec()` 作为更新的 API,它通常是直接替换,但也可以正确处理函数注释和仅关键字参数。 (2认同)