使用* args和lambda函数

时间:2016-09-23 10:37:36

标签: python caching lambda

我最近尝试过谷歌 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秒并且几乎是瞬间)。

以下是我的困惑:

  • 为什么memoize的第三个参数为空?当在 memoize 中打印args时,它会打印()。但不知怎的,mem [key]将func(* args)存储为func(key)?
  • 为什么此行为仅在使用lambda函数时有效(示例将缓存但 example2 不会)?我认为lambda:longrun(n)只是给输入一个返回longrun(n)的函数的简短方法。

作为奖励,有谁知道如何使用装饰器记忆功能?

此外,我想不出一个更具描述性的标题,编辑欢迎。感谢。

1 个答案:

答案 0 :(得分:2)

符号*args代表可变数量的位置参数。例如,print可用作print(1)print(1, 2)print(1, 2, 3)等。同样,**kwargs代表可变数量的关键字参数。

请注意,名称argskwargs只是一种约定 - 它是***符号,使其成为可变参数。

无论如何,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内查找argsmem的方法。如果您曾尝试将list放在dict内,您知道有些类型不适用于映射(或任何其他合适的查找结构)。因此,如果您定义key = f, args,则无法记住采用可变/不可变类型的函数。 Python的functools.lru_cache实际上有这个限制。

    定义明确的key是解决此问题的一种方法。它的优点是呼叫者可以选择适当的密钥,例如在没有任何修改的情况下取n。这提供了最佳的优化潜力。然而,它很容易打破 - 只使用n错过了所调用的实际函数。使用相同的输入记住第二个函数会破坏缓存。

    有其他方法,每种方法都有利弊。常见的是类型的显式转换:listtuplesetfrozenset,依此类推。这很慢,但最精确。另一种方法是在str中调用reprkey = repr((f, args, sorted(kwargs.items()))),但它依赖于具有适当repr的每个值。