Python惰性求值器

Posted

技术标签:

【中文标题】Python惰性求值器【英文标题】:Python lazy evaluator 【发布时间】:2011-07-14 18:45:02 【问题描述】:

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

【问题讨论】:

我不确定这就是 lazy 的通常含义。将其称为 cachingmemoization 更安全。 @John Y 是对的:“惰性求值”是指不计算不会影响包含表达式结果的表达式的结果,例如。在f() and g() 中,如果f()False,则惰性求值不会调用g()。这个问题与此无关。 当有函数参数时它是记忆。否则,它只是一个惰性函数调用。 @Neil G - 它肯定是 cached 函数结果吗?无论如何,g() 至少会被调用一次。 @detly 澄清了我想要什么:g() 无论如何都不会被调用。 【参考方案1】:

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

【讨论】:

我很确定这正是我想要的。【参考方案2】:

尝试使用这个装饰器:

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) (在这里找到:Is there a decorator to simply cache function return values? by Alex Martelli)

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

【讨论】:

【参考方案3】:

你可以使用缓存装饰器,看一个例子

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】:

有很多装饰器用于记忆:

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

【讨论】:

【参考方案5】:

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

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

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

如果您再次编辑它,这是我正在回复的 sn-p:

我有:

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 模块)。

【讨论】:

另外,如果f(Z) 表达式比实际中的要长,只需有两个单独的if 语句,第二个嵌套在第一个的else 子句中。 我正在尝试按照我的想法编写代码,因此我希望在输入 if 之前将 f(Z) 绑定到某个名称。然后如果我需要 f(Z) 的结果,我可以查询它的长度,使用它的值等等,我知道它只会在需要时被创建。

以上是关于Python惰性求值器的主要内容,如果未能解决你的问题,请参考以下文章

Go 表达式求值器

python 短路求值或惰性求值

Python中的惰性求值

Python:any() / all() 中的惰性函数求值

远程调试 msvsmon.exe VS2015 或无法附加到进程时,表达式求值器出现内部错误。 RPC 服务器不可用

惰性求值——lodash源码解读