Python懒惰评估器

时间:2011-03-14 05:20:47

标签: python caching lazy-evaluation memoization

是否有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必须是一个函数,但情况可能并非如此。

7 个答案:

答案 0 :(得分:6)

无论你是寻求缓存还是懒惰评估,我都有点困惑。对于后者,请查看模块lazy.py by Alberto Bertogli

答案 1 :(得分: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 :(得分: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

答案 3 :(得分: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的任何行。这就是装饰者的力量。

答案 4 :(得分:1)

为了完整起见,这里是我的懒惰评估装饰师食谱的链接:

https://bitbucket.org/jsbueno/metapython/src/f48d6bd388fd/lazy_decorator.py

答案 5 :(得分:1)

Here是一个非常简短的懒惰装饰者,虽然它没有使用@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

答案 6 :(得分:0)

即使你的编辑,以及一系列的评论与我有点,我仍然不太明白。在你的第一句话中,你说第一次调用f()应该调用g(),但随后返回缓存的值。但是在你的评论中,你说“g()不会被调用,无论什么”(强调我的)。我不确定你在否定:你是说g()应该从不被调用(没有多大意义;为什么g()存在?);或者g()可能被调用,但可能不会(嗯,这仍然与第一次调用f()时调用g()相矛盾)。然后你给出一个完全不涉及g()的片段,并且实际上与你的问题的第一句话或者与评论线程无关。

如果你再次编辑它,这是我回复的片段:

  

我有:

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

代码有效,但我想   重组它,使f(Z)只   如果使用该值则调用。我不   想要改变的定义   f(...),并且Z缓存有点大。

如果这确实是你的问题,那么答案就是

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

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

我不完全理解“Z缓存有点大”。如果你的意思是在程序执行过程中会有太多不同的Z值,那么memoization就没用了,那么你可能需要预先计算f(Z)的所有值并在运行时查找它们。如果你不能这样做(因为你无法知道你的程序会遇到Z的值),那么你又回到了memoization。如果那仍然太慢,那么你唯一真正的选择是使用比Python更快的东西(尝试Psyco,Cython,ShedSkin或手工编码的C模块)。