如何避免在python中作为参数传递的惰性属性评估

时间:2016-11-19 02:02:35

标签: python lazy-evaluation

我有以下代码来懒惰地计算python 3.5中的值。我也尝试了同样结果的@cached_property decorator,所以为了简单起见,我会使用它。

class Foo:    
    @property
    def expensive_object(self):
        if not hasattr(self, "_expensive_object"):            
            print("Lengthy initialization routine")
            self._expensive_object = 2
        return self._expensive_object           

问题是,当我将它作为参数传递给函数时,它会被评估,即使它最终没有在内部使用,如下例所示:

def bar(some_parameter, another_parameter):
    if some_parameter != 10:
        print(some_parameter)
    else:
        print(another_parameter)

从下面的输出中我们看到它只是通过传递来评估,但它并不是绝对必要的,因为代码没有尝试使用它。

In [23]: foo1 = Foo()
    ...: bar(3, foo1.expensive_object)
         Lengthy initialization routine
         3

In [24]: bar(3, foo1.expensive_object)
         3

在某些情况下,我的脚本可以在不需要评估的情况下运行,但由于这种情况,它最终还是会这样做。 将参数分解出来也是不切实际的。我也在组成成员对象的__init__中使用它。

如果可能的话,我想让这个属性更加懒惰,因为只有在实际阅读时它才能进行评估。

1 个答案:

答案 0 :(得分:4)

Python在您寻求的级别缺乏简单,惯用的懒惰属性评估。

有几种方案可以像这样进行惰性属性评估,但它们涉及被调用函数(bar)的参与和合作。例如。你可以传入一个对象和一个属性名称

def bar2(x, y, propname):
    if x != 10:
        print(x)
    else:
        print(getattr(y, propname))

bar2(3, foo1, 'expensive_object')

或者你可以像lambda一样传入一个可调用的东西:

def bar3(x, y):
    if x != 10:
        print(x)
    else:
        print(y())

bar3(3, lambda: foo1.expensive_object)

但是尽管如此,Python本身就是一种非常简单的语言。 它并没有做很多不做评估 - 如果你不需要 - 它 甚至最简单的C或Java编译器的优化 经常做。它并没有维持几乎形而上学的左值/右值 你在Perl中看到的区别(这在这里真的很有帮助)。 并且它不会尝试动态插入和评估 thunks延迟属性调用。 当您致电foo1.expensive_object时,它会计算该值并且 交给我。如果你希望它以不同的方式完成,你将不得不做出来 重要的其他安排,例如上文。

如果您经常需要延迟/延迟评估,那么定义一个必要的评估帮助函数会很方便:

def get_value(x): 
    return x() if hasattr(x, '__call__') else x

这样,在需要时,您可以在某种程度上规范评估y。使用函数仍然需要合作,但是这允许您在需要时传递静态值,或者当您需要进行评估时传递lambda:

def bar4(x, y):
    if x != 10:
        print(x)
    else:
        print(get_value(y))

bar4(3, 33)  # works
bar4(4, lambda: foo1.expensive_object) # also works!