在 Python 中处理时,如何确保任意函数调用列表不会急切地评估超过短路点?
How do I ensure a list of arbitrary function calls is not eagerly evaluated past the short circuit point while processing in Python?
例如,给定
def expensive_call(x):
print(x)
if x == "d":
return x
def expensive_call_2(x, y):
print(x)
print(y)
return x + y
a = [expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")]
next((e for e in a if e is not None), 'All are Nones')
输出是
a
b
c
d
Out[22]: 'bc'
由于 expensive_call("d")
被急切求值,请注意即使 next
调用在第二次调用时短路并输出 "bc",也会打印 "d"。
我正在对列表 a
中的调用进行硬编码,并且 a
不必是列表数据结构。
一种可能的解决方案如下:
a = ['expensive_call("a")', 'expensive_call_2("b", "c")', 'expensive_call("d")']
def generator():
for e in a:
r = eval(e)
if r is not None:
yield r
next(generator(), 'All are Nones')
输出是
a
b
c
Out[23]: 'bc'
随心所欲。但是,我真的不喜欢必须使用 eval。我也不想使用任何最初将函数指针和参数分开的解决方案,如 (expensive_call, ("a"))
。理想情况下我会有类似
的东西
a = lazy_magic([expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")])
next((e for e in a if e is not None), 'All are Nones')
请注意, 是一个类似的问题,但仅适用于函数具有相同方法签名的情况。
您可以使用以下装饰器:
def lazy_fn(fn):
return lambda *args: lambda: fn(*args)
(如果您喜欢 lambda,也可以表示为 lazy_fn = lambda fn: lambda *args: lambda: fn(*args)
。)
这样使用:
@lazy_fn
def expensive_call(x):
print(x)
if x == "d":
return x
@lazy_fn
def expensive_call_2(x, y):
print(x)
print(y)
return x + y
a = [expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")]
print(next((e for e in map(lambda i: i(), a) if e is not None), 'All are Nones'))
输出:
a
b
c
bc
请注意,您需要使用 for e in map(lambda i: i(), a)
,而不是 for e in a
。
您可以将它们全部放在一个函数中并产生结果:
def gen():
yield expensive_call("a")
yield expensive_call_2("b", "c")
yield expensive_call("d")
result = next(
(value for value in gen() if value is not None),
'All are Nones')
另一种解决方案是使用 partial
应用程序:
from functools import partial
calls = [partial(expensive_call, 'a'),
partial(expensive_call_2, 'b', 'c'),
partial(expensive_call, 'd')]
然后评估:
next((result for call in calls
for result in [call()]
if result is not None),
'All results None')
例如,给定
def expensive_call(x):
print(x)
if x == "d":
return x
def expensive_call_2(x, y):
print(x)
print(y)
return x + y
a = [expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")]
next((e for e in a if e is not None), 'All are Nones')
输出是
a
b
c
d
Out[22]: 'bc'
由于 expensive_call("d")
被急切求值,请注意即使 next
调用在第二次调用时短路并输出 "bc",也会打印 "d"。
我正在对列表 a
中的调用进行硬编码,并且 a
不必是列表数据结构。
一种可能的解决方案如下:
a = ['expensive_call("a")', 'expensive_call_2("b", "c")', 'expensive_call("d")']
def generator():
for e in a:
r = eval(e)
if r is not None:
yield r
next(generator(), 'All are Nones')
输出是
a
b
c
Out[23]: 'bc'
随心所欲。但是,我真的不喜欢必须使用 eval。我也不想使用任何最初将函数指针和参数分开的解决方案,如 (expensive_call, ("a"))
。理想情况下我会有类似
a = lazy_magic([expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")])
next((e for e in a if e is not None), 'All are Nones')
请注意, 是一个类似的问题,但仅适用于函数具有相同方法签名的情况。
您可以使用以下装饰器:
def lazy_fn(fn):
return lambda *args: lambda: fn(*args)
(如果您喜欢 lambda,也可以表示为 lazy_fn = lambda fn: lambda *args: lambda: fn(*args)
。)
这样使用:
@lazy_fn
def expensive_call(x):
print(x)
if x == "d":
return x
@lazy_fn
def expensive_call_2(x, y):
print(x)
print(y)
return x + y
a = [expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")]
print(next((e for e in map(lambda i: i(), a) if e is not None), 'All are Nones'))
输出:
a
b
c
bc
请注意,您需要使用 for e in map(lambda i: i(), a)
,而不是 for e in a
。
您可以将它们全部放在一个函数中并产生结果:
def gen():
yield expensive_call("a")
yield expensive_call_2("b", "c")
yield expensive_call("d")
result = next(
(value for value in gen() if value is not None),
'All are Nones')
另一种解决方案是使用 partial
应用程序:
from functools import partial
calls = [partial(expensive_call, 'a'),
partial(expensive_call_2, 'b', 'c'),
partial(expensive_call, 'd')]
然后评估:
next((result for call in calls
for result in [call()]
if result is not None),
'All results None')