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中的any和all函数

Python-any函数和all函数

[python] 之all()和any()内置函数

python any and all function

如何解决 Python Pandas 中的“系列的真值不明确。使用 an.empty、a.bool()、a.item()、a.any() 或 a.all()”? [复制]

Python学习---django惰性机制