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

How to avoid lazy property evaluation on passing as argument in python

我有以下代码用于延迟计算 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__ 中使用它。

如果可能的话,我想让 属性 更懒惰,因为它应该只在实际读取时才计算。

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 编译器的优化 例行公事。它不维护几乎形而上学的 lvalue/rvalue 你在 Perl 中看到的区别(这在这里真的很有帮助)。 而且它不会尝试动态插入和评估 thunks 延迟 属性 调用。 当您调用 foo1.expensive_object 时,它将计算该值并 交给我。如果你想以不同的方式完成它,你将不得不 重要的其他安排,例如上述。

如果您经常需要延迟/惰性求值,定义一个必要时求值辅助函数会很方便:

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

这样您就可以在需要时稍微规范化对 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!