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

时间:2011-08-22 18:32:08

标签: python numpy

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

现在的示例:

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()

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

5 个答案:

答案 0 :(得分:7)

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

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()而不是lasy_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),您可以将函数调用硬编码到属性中。

答案 1 :(得分:6)

编写一个类更强大,但为了简化而优化(我认为你要求的),我提出了以下解决方案:

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

答案 2 :(得分:5)

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

答案 3 :(得分:2)

你不能像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算法。

答案 4 :(得分:2)

对我来说,似乎适合您的问题的解决方案是将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(),则实际上可以运行代码并实现所需的功能。但是我认为更实用的方法是前一种。