使用*args和lambda函数在python中进行缓存

使用*args和lambda函数在python中进行缓存,python,caching,lambda,Python,Caching,Lambda,我最近尝试了谷歌。在我的时间结束后,我决定尝试找到一个解决方案来解决我无法解决的问题,并找到了一个解决方案(如果你感兴趣,包括问题陈述)。我以前为我想要缓存的每个函数都制作了一个字典,但在这个解决方案中,任何函数/输入都可以使用相同的语法进行缓存 首先,我对代码是如何工作的感到困惑,*args变量没有作为参数输入(并且打印为空)。下面是一个经过修改的最小示例来说明我的困惑: mem = {} def memoize(key, func, *args): """ Helper t

我最近尝试了谷歌。在我的时间结束后,我决定尝试找到一个解决方案来解决我无法解决的问题,并找到了一个解决方案(如果你感兴趣,包括问题陈述)。我以前为我想要缓存的每个函数都制作了一个字典,但在这个解决方案中,任何函数/输入都可以使用相同的语法进行缓存

首先,我对代码是如何工作的感到困惑,*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(n,一个lambda函数,)。函数longrun只是一个身份函数,有很多无用的计算,因此很容易看到缓存是否工作(示例(2)第一次大约需要5秒,之后几乎是瞬间)

以下是我的困惑:

  • 为什么memoize的第三个参数是空的?在中打印args时,它会打印()。然而,mem[key]以某种方式将func(*args)存储为func(key)
  • 为什么此行为仅在使用lambda函数时有效(示例将缓存,但示例2不会)?我认为lambda:longrun(n)只是给出一个返回longrun(n)的函数作为输入的一种简单方法
作为奖励,有人知道如何使用装饰器记忆函数吗

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

该符号表示数量可变的位置参数。例如,
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中是相同的。它只是创建函数对象的不同符号

问题是在
示例2中,您没有传递函数。调用一个函数,然后传递其结果。相反,您必须分别传递函数及其参数

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
    作为闭包从包含范围绑定。因此,您不必将其传递给memoize—它已经绑定在
    非lambda
    中。因此,
    args
    在memoize中为空,即使
    longrun
    确实接收到一个参数,因为这两个参数不直接交互

  • 现在,为什么它是
    mem[key]=f(*args)
    ,而不是
    mem[key]=f(key)
    ?这实际上是一个稍微有点错误的问题;正确的问题是“为什么它不是
    mem[f,args]=f(*args)
    ?”

    记忆化之所以有效,是因为同一函数的相同输入导致相同的输出。也就是说,
    f,args
    标识您的输出。理想情况下,您的
    键应该是
    f,args
    ,因为这是唯一相关的信息

    问题是您需要一种在
    mem
    中查找
    f
    args
    的方法。如果您曾经尝试将
    列表
    放在
    目录
    中,您就会知道有些类型在映射(或任何其他合适的查找结构)中不起作用。因此,如果您定义了
    key=f,args
    ,则无法记忆采用可变/不可损坏类型的函数。Python的
    functools.lru\u缓存实际上有这个限制

    定义显式
    键是解决此问题的一种方法。它的优点是调用者可以选择一个合适的键,例如在不做任何修改的情况下使用
    n
    。这提供了最佳的优化潜力。但是,它很容易中断—仅使用
    n
    会忽略实际调用的函数。用相同的输入记忆第二个函数会破坏缓存

    有几种不同的方法,每种方法都有利弊。常见的是类型的显式转换:
    list
    tuple
    set
    frozenset
    ,等等。这很慢,但最精确。另一种方法是只调用
    str
    repr
    ,如
    key=repr((f,args,sorted(kwargs.items())))
    ,但它依赖于每个值都有一个正确的
    repr

符号表示数量可变的位置参数。例如,
print
可以用作
print(1)
print(1,2)
print(1,2,3)
等等。类似地,
**kwargs
表示数量可变的关键字参数

请注意,名称
args
kwargs
只是一种约定-正是
*
**
符号使它们可变

无论如何,
memoize
使用它来接受func的基本输入。如果func的结果未缓存,则会使用参数调用它。起作用