Python:any() / all() 中的惰性函数求值
Posted
技术标签:
【中文标题】Python:any() / all() 中的惰性函数求值【英文标题】:Python: Lazy Function Evaluation in any() / all() 【发布时间】:2021-01-13 08:41:34 【问题描述】:Python 中的逻辑运算符是惰性的。具有以下定义:
def func(s):
print(s)
return True
调用or
运算符
>>> func('s') or func('t')
's'
只计算第一个函数调用,因为or
识别出表达式的计算结果为
True
,不管第二个函数调用的返回值如何。 and
的行为确实类似。
但是,当以下列方式使用any()
(类似:all()
)时:
>>> any([func('s'), func('t')])
's'
't'
所有函数调用都会被评估,因为内部列表首先被构造,然后any
开始迭代其项目的布尔值。当我们省略列表构造而只写时也会发生同样的情况
>>> any(func('s'), func('t'))
's'
't'
这样我们就失去了any
的力量 短路,这意味着一旦可迭代的第一个元素为真,它就会中断。如果函数调用很昂贵,那么预先评估所有函数是一个很大的损失,并且是对any
的这种能力的浪费。从某种意义上说,这可以称为 Python 陷阱,因为尝试利用 any
的这一特性的用户可能会出乎意料,并且因为 any
通常被认为只是链接 @987654337 序列的另一种语法方式@ 声明。但any
只是短路,而不是懒惰,这就是区别。
any
is accepting an iterable。因此,应该有一种方法可以创建一个迭代器,它不会预先评估其元素,而是将未评估的元素传递给 any
,并让它们仅在 any
内部进行评估,以实现完全惰性评估。
所以,问题是:我们如何使用any
进行真正的惰性函数评估?这意味着:我们如何在不预先评估所有函数调用的情况下创建any
可以使用的函数调用的迭代器?
【问题讨论】:
在 Python 中有各种各样的迭代器产生东西。例如map()
:any(map(func, ('s', 't')))
。大多数人似乎在 Python3 中遇到了相反的问题——他们想要列表输出,而 Python 为他们提供了生成器和地图!
这与any
无关。评估发生在构建列表时。
【参考方案1】:
我们可以使用generator expression,分别传递函数及其参数并只在生成器中进行评估,如下所示:
>>> any(func(arg) for arg in ('s', 't'))
's'
对于具有不同签名的不同函数,这可能如下所示:
any(
f(*args)
for f, args in [(func1, ('s',)), (func2, (1, 't'))]
)
这样一来,any
将在生成器中的一个函数调用评估为 True
时立即停止调用生成器中的 next()
元素,这意味着函数评估是完全惰性的。
wjandrea 在评论中提到了另一种推迟函数评估的巧妙方法:我们也可以使用lambda expressions,如下所示:
>>> any(f() for f in [lambda: func('s'), lambda: func('t')]
's'
【讨论】:
更广泛地说,您可以通过 lambdas 传递表达式:any(e() for e in [lambda: ''.isalnum(), lambda: True, lambda: 0/0])
。这不会引发ZeroDivisionError
,因为永远不会评估0/0
表达式。以上是关于Python:any() / all() 中的惰性函数求值的主要内容,如果未能解决你的问题,请参考以下文章
如何解决 Python Pandas 中的“系列的真值不明确。使用 an.empty、a.bool()、a.item()、a.any() 或 a.all()”? [复制]