为什么Python中`any`实现会触发递归深度超限而`for`循环不会?
输入
sum_possible(2017, [4, 2, 10]) # -> False
触发
RecursionError: maximum recursion depth exceeded的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()内部的子递归时,所有上层的sum_possible调用的栈帧都堆在调用栈里不会被清掉,栈深度会越来越高。而for循环版本里,每一次子递归调用返回之后,对应的栈帧就会立刻弹出释放,调用栈的最大深度就只有最长的那一条递归链的长度,不会一直累积。 - 缓存写入时机的差异:any版本必须等
any()把所有需要的子调用结果都算完,才会把当前amount的结果写入缓存。在any()执行的整个过程中,当前amount的缓存是空的,极端情况下会出现重复递归的情况。而for循环版本不管最后返回True还是False,都会在返回之前就把结果写入缓存,从根源上避免了重复调用。
你用的测试用例里,numbers里所有数都是偶数,目标值2017是奇数,本来就不可能凑出来,所以必须遍历完所有递归路径才能返回False。这种场景下any版本的栈帧会一直堆,很快就超过Python默认的1000层递归深度限制,而for循环版本的栈会及时释放,自然可以正常运行。
内容的提问来源于stack exchange,提问作者HeyZoos
相关产品推荐
相关产品推荐

