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