递归生成器函数结合functools.cache与yield from使用时结果异常的底层原理咨询
递归生成器函数结合functools.cache与yield from使用时结果异常的底层原理咨询
嗨,这个问题其实戳中了生成器和functools.cache之间一个很容易踩的坑,咱们一步步拆解底层逻辑就能明白为啥结果会不对了:
核心冲突:生成器的状态性 vs 缓存的对象复用
首先得搞清楚两个关键点:
- 生成器函数的特性:当你调用一个带
yield的生成器函数时,它不会立刻执行函数体代码,而是直接返回一个生成器对象。这个对象是有状态的——每次迭代(比如next()或者for循环)它才会执行到下一个yield,一旦迭代完所有产出,它就进入「耗尽」状态,再也不会产生任何值。 functools.cache的作用:它缓存的是函数的返回值。对于你的生成器函数来说,返回值就是那个生成器对象本身。也就是说,只要参数(v, i, N)相同,后续调用都会直接返回第一次生成的那个生成器对象。
你的代码里到底发生了什么?
看你代码里的分支:当len(str(v)) % 2 == 0时,会连续两次调用Blinking(...)(参数完全相同),然后用yield from迭代它们的产出。
- 第一次调用
Blinking(...)时,缓存会存储这个生成器对象,迭代它的时候会正常产出所有值,直到生成器耗尽。 - 第二次调用同一个参数的
Blinking(...)时,cache直接返回了之前那个已经耗尽的生成器对象,这时候yield from这个生成器不会产生任何新值——相当于这部分分支的产出直接丢失了,最终统计的res自然就比预期低了。
怎么解决这个问题?
要缓存的应该是生成器产出的结果,而不是生成器对象本身。你可以把递归计算的逻辑抽成一个缓存普通列表的函数,再在生成器里复用这个结果:
from functools import cache def Blinking(v, i, N): # 生成器负责迭代缓存好的结果列表 yield from _blinking_core(v, i, N) @cache def _blinking_core(v, i, N): # 这个函数返回计算好的结果列表,缓存列表而非生成器 result = [] if i >= N: result.append(v) elif v == 0: result.extend(_blinking_core(1, i+1, N)) elif len(str(v)) % 2 == 0: s = str(v) result.extend(_blinking_core(int(s[:len(s)//2]), i+1, N)) result.extend(_blinking_core(int(s[len(s)//2:]), i+1, N)) else: result.extend(_blinking_core(v*2024, i+1, N)) return result res = 0 for v in [0]: for _ in Blinking(v, 0, 25): res += 1 print(res)
这样每次调用_blinking_core都会返回一个新的列表(或者缓存的列表),迭代的时候都是从完整的列表开始,不会出现生成器耗尽的问题。
备注:内容来源于stack exchange,提问作者Galedon
相关产品推荐
相关产品推荐

