如何避免在 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!
我有以下代码用于延迟计算 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!