直接用for遍历无限生成器会卡死,因其依赖StopIteration退出而无限生成器永不抛出该异常;应使用itertools.islice按需截取批次,避免list(g)等全量消耗操作。

为什么直接用 for 循环遍历无限数据会卡死
因为 Python 的普通 for 会尝试调用 __iter__ 和 __next__ 直到抛出 StopIteration,而无限生成器永远不会抛这个异常,循环就永远不退出。真实场景里,你不是要“遍历完”,而是“按需取一批”。
- 必须配合手动控制(比如
next()调用次数)或外部中断条件 - 不能把无限生成器直接丢进
list(g)或sum(g)这类消耗整个迭代器的函数里 - 前端常见的“滚动加载”本质是每次拉
limit=20条,和生成器的itertools.islice(g, 20)行为一致
用 itertools.islice 安全截取批次数据
islice 是最轻量、最符合语义的切片工具——它不预加载、不缓存、不改变原生成器状态,只按需拉取指定数量。
- 写法:
list(itertools.islice(infinite_gen(), 0, 20))拿前 20 条 - 下一页:再调一次
islice(infinite_gen(), 20, 40)——但注意:这会新建一个生成器,不是续着上次的流 - 如果要真正“接续”,得复用同一个生成器对象,比如
g = infinite_gen(); batch1 = list(islice(g, 20)); batch2 = list(islice(g, 20)) - 别用
g[10:20]:生成器不支持索引,会报TypeError: 'generator' object is not subscriptable
带状态的生成器如何管理分页游标
纯数学序列(如 def count(): i=0; while True: yield i; i+=1)靠偏移就能分页;但真实接口常依赖服务端游标(cursor)或页码(page)。这时生成器本身要封装状态。
- 错误写法:每次调用都重置请求参数,导致重复拉第一页
- 正确做法:在生成器内部维护
cursor或page变量,每次yield后更新它 - 示例片段:
def api_stream(): cursor = None while True: data = requests.get("/api/items", params={"cursor": cursor}).json() if not data["items"]: break for item in data["items"]: yield item cursor = data.get("next_cursor") - 这种生成器只能用一次;若需多次分页,建议封装成类,把生成器逻辑转为
__iter__方法
前端“无限滚动”对应的后端生成器怎么防滥用
生成器本身不带限流,客户端狂刷 next() 会导致后端持续计算或反复请求 API。必须加显式约束。
- 在生成器内部加计数器:
if count >= 1000: raise RuntimeError("Too many items requested") - 对耗时操作(如 HTTP 请求)加
time.sleep(0.1)防压垮下游 - 更稳妥的是不暴露裸生成器,而是提供
fetch_batch(size=50, offset=0)这样的函数,由上层控制节奏 - 注意:生成器抛出异常后状态终止,无法再
next();若要恢复,得重新调用生成器函数创建新实例

















