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

mvb*_*tes 1 python lazy-evaluation

我有以下代码用于在python 3.5中延迟计算值。我也尝试过用@cached_property 装饰器获得相同的结果,因此为了简单起见,我将使用它。

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           
Run Code Online (Sandbox Code Playgroud)

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

def bar(some_parameter, another_parameter):
    if some_parameter != 10:
        print(some_parameter)
    else:
        print(another_parameter)
Run Code Online (Sandbox Code Playgroud)

从下面的输出中,我们看到它只是通过传递而得到评估的,但是由于代码没有尝试使用它,因此它不是严格必需的。

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

In [24]: bar(3, foo1.expensive_object)
         3
Run Code Online (Sandbox Code Playgroud)

在某些情况下,我的脚本可以运行而无需评估,但由于这种情况,最终还是要这样做。排除参数也是不实际的。我还在__init__组合成员对象中使用它。

如果可能的话,我想使该属性更加懒惰,因为它仅在实际读取时才进行评估。

Jon*_*ice 5

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

像这样有几种获取惰性属性的方案,但是它们涉及被调用函数(bar)的参与和配合。例如,您可以传递一个对象和一个属性名称

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

bar2(3, foo1, 'expensive_object')
Run Code Online (Sandbox Code Playgroud)

或者,您也可以像lambda这样传递可调用项:

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

bar3(3, lambda: foo1.expensive_object)
Run Code Online (Sandbox Code Playgroud)

但是,尽管有很多改进,但Python本质上还是一种非常简单的语言。即使不需要最简单的C或Java编译器,它也不会做很多不需要评估的优化。它不能保持您在Perl中看到的几乎形而上的左值/右值区别(在这里确实很有用)。而且,它不会尝试动态插入和评估 thunk以延迟属性调用。调用时foo1.expensive_object,它将计算该值并将其移交给他。如果您希望以其他方式进行操作,则必须做出其他重要的安排,例如以上所述。

如果您通常需要延迟/惰性评估,可以方便地定义“必要时评估”辅助功能:

def get_value(x): 
    return x() if hasattr(x, '__call__') else x
Run Code Online (Sandbox Code Playgroud)

这样,您可以在需要时稍微规范化y。using函数仍然必须配合使用,但是这允许您在需要时传递静态值,在需要使评估变得更懒惰时传递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!
Run Code Online (Sandbox Code Playgroud)