在 Python 中处理时,如何确保在短路点之后不会急切地评估任意函数调用列表?

Posted

技术标签:

【中文标题】在 Python 中处理时,如何确保在短路点之后不会急切地评估任意函数调用列表?【英文标题】:How do I ensure a list of arbitrary function calls is not eagerly evaluated past the short circuit point while processing in Python? 【发布时间】:2020-02-26 08:09:25 【问题描述】:

例如,给定

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')

请注意,https://***.com/a/3405828/2750819 是一个类似的问题,但仅适用于函数具有相同方法签名的情况。

【问题讨论】:

如果不分离方法调用和参数,这似乎是不可能的,因为为了像您在示例中那样评估 [expensive_call("a")...],需要评估所有调用。即使您使用生成器推导,推导也必须采用 ((expensive_call("a"), expensive_call_2("b", "c"), expensive_call("d")) for _ in range(1)) 的形式,因为您不知道确切的规格,因此无法使用。 【参考方案1】:

您可以使用以下装饰器:

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

【讨论】:

有趣。我没有想到这一点,但将@lazy_fn 添加到该列表中的每个函数并不能完全保持我想要的简单性。无论如何感谢您的建议。 partial 已经完成了这项工作。【参考方案2】:

您可以将它们全部放在一个函数中并产生结果:

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')

【讨论】:

比我想象的要粗略一点,但是用逗号替换yield就足够眯眼了,这基本上就像我在写一个列表结构一样。我会用这个,谢谢。 我不确定我是否理解“粗鲁”和“眯眼”。 我喜欢这个,因为我可以在我现有的列表中使用正则表达式,并且基本上可以将每个逗号转换为一个收益。然后删除已有的“[”和“]”,添加第一个“yield”,再添加函数定义。在大约 15 秒的操作中,我现在有了一个“惰性列表”。因此,如果我“眯眼”,整个结构看起来就像一个简单的列表,每一行都有一个元素。 我添加了一个使用partial的示例。 @rassar 我解释说他们不想管理自己的分裂和重组。

以上是关于在 Python 中处理时,如何确保在短路点之后不会急切地评估任意函数调用列表?的主要内容,如果未能解决你的问题,请参考以下文章

如何防止短路评估?

python中不同的异常类型,如何进行异常处理?

POJ 3255(dijkstra优化,次最短路)

Dijkstra算法求单源最短路

处理CSV数据时如何忽略第一行数据?

处理CSV数据时如何忽略第一行数据?