Python惰性求值

10

有没有一种Pythonic的方法来封装一个懒惰函数调用,在第一次使用函数f()时,它调用先前绑定的函数g(Z),在后续调用f()时返回缓存的值?

请注意,备忘录可能并不完美适合。

我有:

f = g(Z)
if x:
     return 5
elif y:
     return f
elif z:
     return h(f)

代码可以运行,但我想重构它,以便只有在使用该值时才调用g(Z)。我不想改变g(...)的定义,并且Z有点大无法缓存。
编辑:我假设f必须是一个函数,但情况可能并非如此。

3
我不确定这是否通常所指的“懒惰”。更安全的叫法是“缓存”或“记忆化”。 - John Y
1
@John Y 是正确的:“惰性求值”是指不计算不会影响包含表达式结果的表达式的结果,例如在 f() and g() 中,如果 f()False,则惰性求值不会调用 g()。这个问题不是关于这个的。 - detly
当函数有参数时,它就是记忆化。否则,它只是一个惰性函数调用。 - Neil G
@Neil G - 肯定是一个缓存的函数结果吧?无论如何,g()至少会被调用一次。 - detly
@Neil G - 啊,现在更有意义了 :) - detly
显示剩余2条评论
8个回答

8

我非常确定这正是我正在寻找的。 - Neil G

3

尝试使用这个装饰器:

class Memoize:
    def __init__ (self, f):
        self.f = f
        self.mem = {}
    def __call__ (self, *args, **kwargs):
        if (args, str(kwargs)) in self.mem:
            return self.mem[args, str(kwargs)]
        else:
            tmp = self.f(*args, **kwargs)
            self.mem[args, str(kwargs)] = tmp
            return tmp

(从失效链接中提取:http://snippets.dzone.com/posts/show/4840 / https://web.archive.org/web/20081026130601/http://snippets.dzone.com/posts/show/4840) (由Alex Martelli在此处发现:Is there a decorator to simply cache function return values?
编辑:这里还有另一种形式的属性(使用__get__):http://code.activestate.com/recipes/363602/

2

您好,您想知道为什么在这种情况下不使用lambda表达式吗?

f = lambda: g(z)
if x:
    return 5
if y:
    return f()
if z:
    return h(f())

我现在想不出有什么好理由。我十年前写了这个问题。 - Neil G

1
你可以使用缓存装饰器,让我们看一个例子。
from functools import wraps

class FuncCache(object):
    def __init__(self):
        self.cache = {}

    def __call__(self, func):
        @wraps(func)
        def callee(*args, **kwargs):
            key = (args, str(kwargs))
            # see is there already result in cache
            if key in self.cache:
                result = self.cache.get(key)
            else:
                result = func(*args, **kwargs)
                self.cache[key] = result
            return result
        return callee

使用缓存装饰器,你可以在这里编写代码

my_cache = FuncCache()

@my_cache
def foo(n):
    """Expensive calculation

    """
    sum = 0
    for i in xrange(n):
        sum += i
    print 'called foo with result', sum
    return sum

print foo(10000)
print foo(10000)
print foo(1234)

从输出结果可以看出

called foo with result 49995000
49995000
49995000

foo函数只会被调用一次。您不需要更改任何foo函数的代码行。这就是装饰器的威力。


1

这里有一个相当简短的懒惰装饰器,虽然它缺少使用@functools.wraps(实际上返回了Lazy的一个实例以及其他一些潜在的问题):

class Lazy(object):
    def __init__(self, calculate_function):
        self._calculate = calculate_function

    def __get__(self, obj, _=None):
        if obj is None:
            return self
        value = self._calculate(obj)
        setattr(obj, self._calculate.func_name, value)
        return value


# Sample use:

class SomeClass(object):

    @Lazy
    def someprop(self):
        print 'Actually calculating value'
        return 13


o = SomeClass()
o.someprop
o.someprop

1

有相当多的装饰器可用于记忆化:

http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize http://code.activestate.com/recipes/498110-memoize-decorator-with-o1-length-limited-lru-cache/ http://code.activestate.com/recipes/496879-memoize-decorator-function-with-cache-size-limit/

想出一个完全通用的解决方案比你想象的要难。例如,你需要注意非可哈希函数参数,并确保缓存不会变得太大。

如果你真的在寻找一种懒惰的函数调用(只有在需要值时才实际评估函数),那么你可能可以使用生成器来实现。

编辑:所以我想你真正想要的是惰性求值。这里有一个库,可能是你正在寻找的:

http://pypi.python.org/pypi/lazypy/0.5


0
即使在您的编辑和与detly的一系列评论之后,我仍然不太理解。在您的第一句话中,您说第一次调用f()应该调用g(),但随后返回缓存值。但是在您的评论中,您说“无论如何都不会调用g()”(重点在于我)。我不确定您是否否定了什么:您是否在说g()永远不应该被调用(这没有多大意义;为什么g()存在?);还是说g()可能会被调用,但也可能不会(好吧,这仍然与第一次调用f()时调用g()相矛盾)。然后,您提供了一个根本不涉及g()的片段,实际上与您的问题的第一句话或与detly的评论线程都没有关系。
如果您再次编辑,请参考以下代码片段:

I have:

a = f(Z)
if x:
     return 5
elif y:
     return a
elif z:
     return h(a)

The code works, but I want to restructure it so that f(Z) is only called if the value is used. I don't want to change the definition of f(...), and Z is a bit big to cache.

如果这真的是你的问题,那么答案很简单

if x:
    return 5
elif y:
    return f(Z)
elif z:
    return h(f(Z))

这就是如何实现“只有在使用值时才调用f(Z)”。

我不完全理解“Z太大而无法缓存”。如果您的意思是程序执行过程中Z的不同值太多,记忆化是无用的,那么也许您必须采取预计算所有f(Z)的值并在运行时查找它们的方法。如果您无法做到这一点(因为您无法知道程序将遇到的Z的值),则回到记忆化。如果仍然太慢,则您唯一的选择是使用比Python更快的东西(尝试Psyco、Cython、ShedSkin或手写的C模块)。


或者,如果f(Z)表达式在实践中比较长,只需使用两个单独的if语句,第二个嵌套在第一个的else子句中。 - ncoghlan
我试图按照自己的思路编写代码,因此我希望在进入“if”之前将f(Z)绑定到某个名称。然后,如果我需要f(Z)的结果,我可以查询它的长度、使用它的值等,我知道只有在需要时才会创建它。 - Neil G

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接