You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

递归生成器函数结合functools.cache与yield from使用时结果异常的底层原理咨询

递归生成器函数结合functools.cache与yield from使用时结果异常的底层原理咨询

嗨,这个问题其实戳中了生成器和functools.cache之间一个很容易踩的坑,咱们一步步拆解底层逻辑就能明白为啥结果会不对了:

核心冲突:生成器的状态性 vs 缓存的对象复用

首先得搞清楚两个关键点:

  1. 生成器函数的特性:当你调用一个带yield的生成器函数时,它不会立刻执行函数体代码,而是直接返回一个生成器对象。这个对象是有状态的——每次迭代(比如next()或者for循环)它才会执行到下一个yield,一旦迭代完所有产出,它就进入「耗尽」状态,再也不会产生任何值。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 17:43:03