Python:any() / all() 中的惰性函数求值

jon*_*ach 8 python lazy-evaluation short-circuiting any

Python 中的逻辑运算符是惰性的。具有以下定义:

def func(s):
    print(s)
    return True
Run Code Online (Sandbox Code Playgroud)

呼叫or接线员

>>> func('s') or func('t')
's'
Run Code Online (Sandbox Code Playgroud)

只计算第一个函数调用,因为or识别表达式计算为 True,而不管第二个函数调用的返回值。and行为类似。

但是,当以下列方式使用any()(类比:)时all()

>>> any([func('s'), func('t')])
's'
't'
Run Code Online (Sandbox Code Playgroud)

所有函数调用都会被评估,因为在any开始迭代其项目的布尔值之前,首先构造内部列表。当我们省略列表构造而只写时,也会发生同样的情况

>>> func('s') or func('t')
's'
Run Code Online (Sandbox Code Playgroud)

这样,我们失去的力量any短路,它只要迭代的第一个元素是truish打破该装置。如果函数调用代价高昂,那么预先评估所有函数是一个很大的损失,并且浪费了any. 从某种意义上说,人们可以将其称为 Python 陷阱,因为对于尝试利用 的此功能的用户来说可能出乎意料any,并且any通常被认为只是链接一系列or语句的另一种语法方式。但any只是短路,而不是懒惰,这就是这里的区别。

any正在接受一个 iterable。因此,应该有一种创建迭代器的方法,该迭代器不会预先评估其元素,而是将未评估的元素传递给any并让它们any仅在内部评估,以实现完全懒惰的评估。

所以,问题是:我们如何使用any真正的惰性函数评估?这意味着:我们如何制作一个any可以消费的函数调用迭代器,而无需预先评估所有函数调用?

jon*_*ach 9

我们可以使用生成器表达式,分别传递函数及其参数,并仅在生成器中进行评估,如下所示:

>>> any(func(arg) for arg in ('s', 't'))
's'
Run Code Online (Sandbox Code Playgroud)

对于具有不同签名的不同函数,这可能如下所示:

any(
    f(*args)
    for f, args in [(func1, ('s',)), (func2, (1, 't'))]
)
Run Code Online (Sandbox Code Playgroud)

这样,只要生成器中的一个函数调用计算为,any就会停止调用next()生成器中的元素True,这意味着函数计算是完全惰性的。

wjandrea在评论中提到了另一种推迟函数计算的巧妙方法:我们也可以使用lambda 表达式,如下所示:

>>> any(f() for f in [lambda: func('s'), lambda: func('t')]
's'
Run Code Online (Sandbox Code Playgroud)

  • 更广泛地说,您可以通过 lambda 传递表达式:`any(e() for e in [lambda: ''.isalnum(), lambda: True, lambda: 0/0])`。这不会引发“ZeroDivisionError”,因为“0/0”表达式永远不会被计算。 (2认同)