8

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

4

8 回答 8

7

无论您是寻求缓存还是惰性评估,我都感到有些困惑。对于后者,请查看 Alberto Bertogli 的模块lazy.py。

于 2011-03-14T10:07:53.910 回答
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)

编辑:这是另一种形式的属性(使用__get__http://code.activestate.com/recipes/363602/

于 2011-03-14T05:23:50.223 回答
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

于 2011-03-14T05:31:23.707 回答
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 的任何行。这就是装饰器的力量。

于 2011-03-14T05:35:25.710 回答
1

为了完整起见,这里是我的惰性评估器装饰器配方的链接:

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

于 2012-04-02T14:24:13.860 回答
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
于 2014-08-18T09:49:39.597 回答
0

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

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

我有:

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 值以至于记忆是无用的,那么您可能不得不求助于预先计算 f(Z) 的所有值并在运行时查找它们。如果你不能这样做(因为你不知道你的程序将遇到的 Z 的值),那么你就回到记忆中。如果这仍然太慢,那么您唯一真正的选择是使用比 Python 更快的东西(尝试 Psyco、Cython、ShedSkin 或手动编码的 C 模块)。

于 2011-03-14T07:12:24.107 回答
0

好奇为什么在这种情况下不只使用 lambda?

f = lambda: g(z)
if x:
    return 5
if y:
    return f()
if z:
    return h(f())
于 2022-02-04T17:23:10.303 回答