我有一个函数需要很长时间,并且需要能够在使用相同参数再次调用它时缓存自己的结果。看下面的例子似乎解决了这个问题。我正在使用Python 3.6
我的问题围绕着这一行:
param_sig = repr(locals())
1)是否有更多的Pythonic方法来获取传递给函数的参数的唯一签名?
2)我可以依靠Python的函数参数的插入顺序进入locals()映射吗?同样,这似乎有效,但如果需要,我可以在一个不太优雅的签名创建者中明确地重新使用每个参数,如:
param_sig = "{},{},{}".format(a,b,c)
示例代码:
import random
cached_answers = {}
def f(a=1, b=2, c=3):
param_sig = repr(locals())
if param_sig in cached_answers:
ans = cached_answers[param_sig]
print("Call: {} = CACHED {}".format(param_sig,ans))
return ans
else:
# do heavy lifting then cache the result
ans = random.random()
print("Call: {} = {}".format(param_sig,ans))
cached_answers[param_sig] = ans
return ans
# various calls... some of which are repeated and should be cached
f()
f(b=9)
f(c=9, a=9)
f() # should be cached
parms={'a':9}
f(**parms)
f(b=9) # should be cached
f(a=9) # should be cached
答案 0 :(得分:1)
repr(locals())
非常糟糕,因为它不适用于此功能。它可以工作,或者可以工作,我可以挖掘除了这样做的语义之外的一些问题。
我将回到这个问题 - 首先,你的解决方案:
Python有一个缓存函数,就像你在stdlib上的functools
模块中所需要的那样 - 只需导入它并将其用作装饰器:
from functools import lru_cache
@lru_cache()
def f(a=1, b=2, c=3):
# just put the heavy stuff inside your original "else" clause here
现在要理解为什么装饰器在那里有更好的解决方案:缓存结果的逻辑根本不与函数的逻辑混合在一起,而且,无论使用什么方法进行缓存都可以用于缓存任何函数。你的程序 - 不需要复制每个函数体内的缓存代码。
您将了解到虽然Python自己的lru_cache
与您的案例匹配,但它并不是最佳匹配,并非适用于所有情况 - 无论如何,您最好安装一个用于缓存或滚动自己的缓存的第三方软件包,但保持逻辑分离。
可以应用于系统中各种类似函数或方法的某种编程逻辑的想法也称为"aspect oriented programing",而Python装饰器语法是一种廉价使用它的好方法。
除了将逻辑与函数分离,并使用repr(locals())
来序列化参数之外,您的方法是正确的:保持(模块)全局字典与每组参数的结果是通常的方法缓存功能。 lru_cache
恰好以透明的方式发生。
答案 1 :(得分:0)
通过使用* args(提供位置参数列表)或** kwargs(“关键字”参数的dict)语法提供参数,您可以以确定的顺序访问参数。考虑:
ID PET_NAME PET_DESC OWNER
1 Kibbles Rat-faced possum Bixby
2 Kibbles Rat-faced possum Dweezle
或
>>> def fn(*args):
... for i in args: print(i)
...
>>> fn('a', 'b', 'c')
a
b
c
要在缓存中使用它们,您需要将args转换为dict键。 我建议您使用元组作为缓存键,而不是将其格式化为字符串。元组是不可变的,因此它们可以被散列(对dicts的要求)。例如:
>>> def fn2(**kwargs):
... for k in kwargs.keys(): print("{}: {}".format(k, kwargs[k]))
...
>>> fn2(a='aval', b='bval', c='cval')
a: aval
c: cval
b: bval