python惰性变量?或者,延迟昂贵的计算

Posted

技术标签:

【中文标题】python惰性变量?或者,延迟昂贵的计算【英文标题】:python lazy variables? or, delayed expensive computation 【发布时间】:2011-11-01 09:17:27 【问题描述】:

我有一组非常大且计算成本高的数组,在任何给定的运行中,我的代码不一定需要所有数组。我想让他们的声明成为可选的,但理想情况下不必重写我的整个代码。

现在的例子:

x = function_that_generates_huge_array_slowly(0)
y = function_that_generates_huge_array_slowly(1)

我想做的例子:

x = lambda: function_that_generates_huge_array_slowly(0)
y = lambda: function_that_generates_huge_array_slowly(1)
z = x * 5 # this doesn't work because lambda is a function
      # is there something that would make this line behave like
      # z = x() * 5?
g = x * 6

虽然使用上面的 lambda 实现了预期的效果之一 - 数组的计算被延迟到需要它时 - 如果您多次使用变量“x”,则必须每次都计算它。我只想计算一次。

编辑: 经过一些额外的搜索,看起来可以(大约)使用类中的“惰性”属性(例如http://code.activestate.com/recipes/131495-lazy-attributes/)来做我想做的事情。我不认为有任何方法可以在不创建单独的类的情况下做类似的事情吗?

EDIT2:我正在尝试实施一些解决方案,但我遇到了一个问题,因为我不明白以下之间的区别:

class sample(object):
    def __init__(self):
        class one(object):
            def __get__(self, obj, type=None):
                print "computing ..."
                obj.one = 1
                return 1
        self.one = one()

class sample(object):
    class one(object):
        def __get__(self, obj, type=None):
            print "computing ... "
            obj.one = 1
            return 1
    one = one()

我认为这些变量是我正在寻找的,因为昂贵的变量旨在成为类的一部分。

【问题讨论】:

***.com/questions/5078726/… 是我正在尝试做的更有用的懒惰实现 【参考方案1】:

问题的前半部分(重用值)很容易解决:

class LazyWrapper(object):
    def __init__(self, func):
        self.func = func
        self.value = None
    def __call__(self):
        if self.value is None:
            self.value = self.func()
        return self.value

lazy_wrapper = LazyWrapper(lambda: function_that_generates_huge_array_slowly(0))

但您仍然必须将其用作lazy_wrapper() 而不是lazy_wrapper

如果您要多次访问某些变量,使用它可能会更快:

class LazyWrapper(object):
    def __init__(self, func):
        self.func = func
    def __call__(self):
        try:
            return self.value
        except AttributeError:
            self.value = self.func()
            return self.value

这将使第一次调用更慢,而后续使用更快。

编辑:我看到您找到了一个类似的解决方案,要求您在类上使用属性。无论哪种方式都需要您重写每个惰性变量访问,因此请选择您喜欢的任何一种。

编辑2:你也可以这样做:

class YourClass(object)
    def __init__(self, func):
        self.func = func
    @property
    def x(self):
        try:
            return self.value
        except AttributeError:
            self.value = self.func()
            return self.value

如果您想访问x 作为实例属性。不需要额外的课程。如果您不想更改类签名(通过使其需要 func),您可以将函数调用硬编码到属性中。

【讨论】:

也许如果您使用属性而不是__call__,则可以避免(),并且更容易搜索/替换。这取决于现有的代码。 我看不出它对搜索/替换有何影响?而您只是用其他东西替换(),这似乎是一个您更喜欢的主观问题。我的方法只是实现它的最少代码方式。 我喜欢第二个 LazyWrapper... 我会试一试。我发现的解决方案有太多抽象层,我无法在上下文中理解。 如果你想延迟加载类的属性,只需使用property,如我的编辑所示。 谢谢,但有一个警告:我不能在类声明中设置属性,它必须在方法中。幸运的是,这个问题在这里得到了明确的回答:***.com/questions/5078726/…。因此,将您的建议与该解决方案相结合,我很确定我有我的答案。【参考方案2】:

编写一个类更健​​壮,但为了简单而优化(我认为您正在要求),我想出了以下解决方案:

cache = 

def expensive_calc(factor):
    print 'calculating...'
    return [1, 2, 3] * factor

def lookup(name):
    return ( cache[name] if name in cache
        else cache.setdefault(name, expensive_calc(2)) )

print 'run one'
print lookup('x') * 2

print 'run two'
print lookup('x') * 2

【讨论】:

这不是一个坏主意,因为它更简单,但它需要在我的代码中进行更多的重写。我先试试上面的 LazyWrapper。 Gringo - 除了上述建议外,我最终还使用了您的建议(同一代码中的两个不同用例),但 agf 的回答更直接地回答了我所问的问题。谢谢! 好的,但是您特别要求提供无类解决方案,这就是我想出这个的原因。顺便说一句,使用 defaultdict 是我的另一个想法。【参考方案3】:

Python 3.2 及更高版本在 functools 模块中实现了 LRU 算法以处理简单的缓存/记忆情况:

import functools

@functools.lru_cache(maxsize=128) #cache at most 128 items
def f(x):
    print("I'm being called with %r" % x)
    return x + 1

z = f(9) + f(9)**2

【讨论】:

另见:***.com/questions/815110/…【参考方案4】:

你不能用一个简单的名字,比如x,来真正懒惰地评估。名称只是哈希表中的一个条目(例如,在 locals()globals() 返回的条目中)。除非您修补这些系统表的访问方法,否则您无法将代码的执行附加到简单的名称解析中。

但是您可以以不同的方式将函数包装在缓存包装器中。 这是一种OO方式:

class CachedSlowCalculation(object):
    cache =  # our results

    def __init__(self, func):
        self.func = func

    def __call__(self, param):
        already_known = self.cache.get(param, None)
        if already_known:
            return already_known
        value = self.func(param)
        self.cache[param] = value
        return value

calc = CachedSlowCalculation(function_that_generates_huge_array_slowly)

z = calc(1) + calc(1)**2 # only calculates things once

这是一种无类方式:

def cached(func):
    func.__cache =  # we can attach attrs to objects, functions are objects
    def wrapped(param):
        cache = func.__cache
        already_known = cache.get(param, None)
        if already_known:
            return already_known
        value = func(param)
        cache[param] = value
        return value
    return wrapped

@cached
def f(x):
    print "I'm being called with %r" % x
    return x + 1

z = f(9) + f(9)**2 # see f called only once

在现实世界中,您将添加一些逻辑以将缓存保持在合理的大小,可能使用 LRU 算法。

【讨论】:

什么是 LRU 算法?我喜欢你解决问题的方法——它类似于上面的 Gringo,但在某些情况下可能更优雅——但我的问题实际上可以通过类属性很好地解决。我实际上并不需要懒惰地评估全局变量“x”;我需要懒惰地评估类属性。不过,您的解决方案适用于前一种情况。 出于好奇,但是... locals() dict/hashtable 本身是一个可以覆盖属性的类吗?我认为这将是一个糟糕的想法,但原则上可能吗? LRU = Least Recently Used:一旦你创建或访问了一个缓存条目,你就把它放到列表的开头;如果列表太长,最近最少使用的条目将被丢弃。 locals()globals() 都返回一个带有只读方法槽的基于 C 的 dict,没有覆盖的机会。 谢谢,很高兴知道这两个。 LRU 可能很快就会派上用场……【参考方案5】:

对我来说,您的问题的正确解决方案似乎是子类化一个 dict 并使用它。

class LazyDict(dict):
    def __init__(self, lazy_variables):
        self.lazy_vars = lazy_variables
    def __getitem__(self, key):
        if key not in self and key in self.lazy_vars:
            self[key] = self.lazy_vars[key]()
        return super().__getitem__(key)

def generate_a():
    print("generate var a lazily..")
    return "<a_large_array>"

# You can add more variables as many as you want here
lazy_vars = 'a': generate_a

lazy = LazyDict(lazy_vars)

# retrieve the variable you need from `lazy`
a = lazy['a']
print("Got a:", a)

如果您使用exec 运行您的代码,您可以实际延迟评估变量。解决方案只是使用自定义全局变量。

your_code = "print('inside exec');print(a)"
exec(your_code, lazy)

如果您使用your_code = open(your_file).read(),您实际上可以运行您的代码并实现您想要的。但我认为更实用的方法是前一种。

【讨论】:

以上是关于python惰性变量?或者,延迟昂贵的计算的主要内容,如果未能解决你的问题,请参考以下文章

全局变量/常量如何在 swift 中变得懒惰

C++ lazy evaluation(延迟计算或惰性求值)介绍

为啥惰性变量/计算属性,而不仅仅是方法

scala的lazy关键字

Swift 之惰性求值

惰性变量定义后括号做啥?