【问题标题】:Memoization/Caching with default optional arguments带有默认可选参数的记忆/缓存
【发布时间】:2016-08-14 18:55:24
【问题描述】:

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

【问题讨论】:

    标签: python caching memoization


    【解决方案1】:

    您可以使用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() 在问题中的整个调用集中只调用一次。每次调用都使用位置指定的所有三个参数。

    【讨论】:

    • 我尝试了你的建议,但它不起作用。例如,连续执行add(1,2)add(1, 2, negative=False) 两次都会得到Computing \\ 3,因此第二次调用正在计算而不是从缓存中返回。
    • @JonWarneke:我明白你的意思。我已经扩展了我的答案以包含完整的解决方案。
    • 对于除了常规参数之外还接受 **kwargs 的函数,这似乎被破坏了。
    • inspect.getargspec 自 3.0 版起已弃用:使用 getfullargspec() 更新 API,通常是直接替换,但也能正确处理函数注释和关键字-只有参数。
    【解决方案2】:

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

    【讨论】:

    • 自 3.5 版起已弃用:改用 Signature.bind()Signature.bind_partial()
    猜你喜欢
    • 2019-07-30
    • 1970-01-01
    • 2011-11-06
    • 1970-01-01
    • 2021-11-24
    • 1970-01-01
    • 1970-01-01
    • 2021-07-05
    • 1970-01-01
    相关资源
    最近更新 更多