为何使用`any`会导致Python程序挂起/运行极慢,而使用`for`循环却能高效执行?
为什么用
any()的递归实现比for循环慢这么多? 先看问题场景:我们要判断能否用给定列表中的数字(可重复使用)相加得到目标金额,示例如下:
sum_possible(2017, [4, 2, 10]) # -> False
性能拉胯的any()实现
这个版本会在处理较大金额时挂起或运行极慢:
def sum_possible(amount, numbers, cache = None): if cache is None: cache = {} if amount in cache: return cache[amount] if amount == 0: return True if amount < 0: return False cache[amount] = any([sum_possible(amount - number, numbers, cache) for number in numbers]) return cache[amount]
高效的for循环实现
这个版本能在合理时间内解决问题:
def sum_possible(amount, numbers, cache = None): if cache is None: cache = {} if amount in cache: return cache[amount] if amount == 0: return True if amount < 0: return False for number in numbers: if sum_possible(amount - number, numbers, cache): cache[amount] = True return True cache[amount] = False return False
核心原因:列表推导式破坏了any()的短路特性
你说的没错,any()本身是支持短路求值的——只要遇到第一个True就会立刻返回,不再检查后续元素。但问题出在你传给any()的是列表推导式:
列表推导式是贪婪求值的,它会先遍历所有numbers元素,执行每一次sum_possible(amount - number, ...)递归调用,把所有结果都计算出来并存进列表,之后any()才会开始遍历这个列表。哪怕第一个递归调用就返回了True,后面的所有递归也已经跑完了,完全没享受到短路的好处!
而for循环的实现则是真正的短路:每调用一次递归,只要返回True,立刻就会写入缓存并返回函数,后面的循环迭代直接跳过,避免了大量不必要的递归计算。
修复any()版本的小技巧
如果想保留any()的写法同时获得和for循环一样的性能,只需要把列表推导式改成生成器表达式(去掉方括号):
cache[amount] = any(sum_possible(amount - number, numbers, cache) for number in numbers)
生成器是惰性求值的,它只会在any()需要的时候才逐个生成元素,遇到第一个True就会停止生成,完美实现短路效果,性能和for循环版本几乎一致。
内容的提问来源于stack exchange,提问作者HeyZoos
相关产品推荐
相关产品推荐

