我最近尝试过谷歌 foo.bar challenge。在我的时间到了之后,我决定尝试找到我不能做的问题的解决方案并找到解决方案here (如果你感兴趣,请包括问题陈述)。我以前一直在为我想要缓存的每个函数创建一个字典,但在这个解决方案中,任何函数/输入都可以使用相同的语法进行缓存。
首先,我对代码如何工作感到困惑,* args变量不作为参数输入(并且打印为空)。下面是一个修改过的最小例子来说明我的困惑:
mem = {}
def memoize(key, func, *args):
"""
Helper to memoize the output of a function
"""
print(args)
if key not in mem:
# store the output of the function in memory
mem[key] = func(*args)
return mem[key]
def example(n):
return memoize(
n,
lambda: longrun(n),
)
def example2(n):
return memoize(
n,
longrun(n),
)
def longrun(n):
for i in range(10000):
for j in range(100000):
2**10
return n
这里我使用相同的 memoize 功能,但带有打印功能。函数示例返回 memoize(n,lambda函数)。函数 longrun 只是一个具有大量无用计算的标识函数,因此很容易看出缓存是否正常工作( example(2)将在第一次使用~5秒并且几乎是瞬间)。
以下是我的困惑:
作为奖励,有谁知道如何使用装饰器记忆功能?
此外,我想不出一个更具描述性的标题,编辑欢迎。感谢。
答案 0 :(得分:2)
符号*args
代表可变数量的位置参数。例如,print
可用作print(1)
,print(1, 2)
,print(1, 2, 3)
等。同样,**kwargs
代表可变数量的关键字参数。
请注意,名称args
和kwargs
只是一种约定 - 它是*
和**
符号,使其成为可变参数。
无论如何,memoize
使用它来接受 func 的基本任何输入。如果 func 的结果未被缓存,则使用参数调用它。在函数调用中,*args
基本上与函数定义中的*args
相反。例如,以下内容是等效的:
# provide *args explicitly
print(1, 2, 3)
# unpack iterable to *args
arguments = 1, 2, 3
print(*arguments)
如果args
为空,则调用print(*args)
与调用print()
相同 - 不会传递任何参数。
函数和lambda函数在python中相同。它只是创建函数对象的另一种表示法。
问题是在example2
中,您没有传递函数。你调用一个函数,然后传递它的结果。相反,你必须分别传递函数及其参数。
def example2(n):
return memoize(
n,
longrun, # no () means no call, just the function object
# all following parameters are put into *args
n
)
现在,一些实现细节:为什么args
为空,为什么有单独的密钥?
空args
来自您对lambda的定义。为了清楚起见,我们将其写成一个函数:
def example3(n):
def nonlambda():
return longrun(n)
return memoize(n, nonlambda)
请注意nonlambda
如何使用无参数。参数n
从包含范围绑定为闭包bound from the containing scope。因此,您不必将其传递给memoize - 它已经绑定在nonlambda
内。因此,args
在memoize中为空,即使longrun
确实收到参数,因为两者不会直接互动。
现在,为什么它是mem[key] = f(*args)
,而不是mem[key] = f(key)
?这实际上是一个错误的问题;正确的问题是"为什么不是mem[f, args] = f(*args)
?"。
记忆效果正常,因为同一功能的相同输入会产生相同的输出。也就是说,f, args
标识您的输出。理想情况下,您的key
将是f, args
,因为它是唯一的相关信息。
问题是您需要一种在f
内查找args
和mem
的方法。如果您曾尝试将list
放在dict
内,您知道有些类型不适用于映射(或任何其他合适的查找结构)。因此,如果您定义key = f, args
,则无法记住采用可变/不可变类型的函数。 Python的functools.lru_cache
实际上有这个限制。
定义明确的key
是解决此问题的一种方法。它的优点是呼叫者可以选择适当的密钥,例如在没有任何修改的情况下取n
。这提供了最佳的优化潜力。然而,它很容易打破 - 只使用n
错过了所调用的实际函数。使用相同的输入记住第二个函数会破坏缓存。
有其他方法,每种方法都有利弊。常见的是类型的显式转换:list
到tuple
,set
到frozenset
,依此类推。这很慢,但最精确。另一种方法是在str
中调用repr
或key = repr((f, args, sorted(kwargs.items())))
,但它依赖于具有适当repr
的每个值。