Python Generator调用next方法异常:提前全量迭代问题排查
API分页生成器
page_helper的问题分析 问题场景
我编写了一个用于API分页的生成器page_helper,代码如下:
def page_helper(req, timeout=5, page=1, **kwargs): print(f"Page {page}", end="\r") try: response = req(params={**kwargs, "page": page}) response = response.json() except Exception as e: status = response.status_code if status == "429": print(f"Rate limited. Waiting {timeout} seconds.") time.sleep(timeout) yield from page_helper(req, page=page, **kwargs) else: raise e else: if len(response) == kwargs["limit"]: yield from page_helper(req, page=page + 1, **kwargs) yield response
使用时希望每次获取一页数据作为批次,处理完成后再获取下一页,但调用next()或遍历生成器时,会直接完成全量迭代,先拉取所有页面才开始处理。示例调用代码:
batches = page_helper(<some_request>, limit=100) # get insert and updates per batch for i, batch in enumerate(batches): print(f"Batch {i + 1}", end="\r") insert_batch = [] update_batch = [] # ... process batch
调用next(batches)时直接执行完整迭代,而非仅返回一个批次,请问这个生成器代码存在什么问题?
问题核心原因
问题出在递归调用与yield的顺序错误:
- 当前代码中,当判断当前页数据量等于
limit(说明还有下一页)时,会先通过yield from page_helper(..., page=page+1, ...)递归拉取下一页及所有后续页面的全部数据,等所有递归的生成器都完成迭代后,才会执行yield response返回当前页的数据。 - 这直接导致生成器启动后,会一次性递归遍历所有分页,把所有页面的数据提前拉取完毕,完全丧失了生成器“按需生成、分批返回”的特性。
此外还有两个细节错误:
- 异常捕获块中,
try块抛出异常时response可能未定义,直接访问response.status_code会触发NameError。 - 状态码判断用了字符串
"429",但实际status_code是整数类型,该判断永远不会生效。
修正后的代码
调整yield与递归调用的顺序,确保先返回当前页数据,再按需递归拉取下一页:
import time def page_helper(req, timeout=5, page=1, **kwargs): print(f"Page {page}", end="\r") try: response = req(params={**kwargs, "page": page}) response_data = response.json() except Exception as e: # 先确认异常对象包含响应信息,再判断状态码 if hasattr(e, 'response') and e.response.status_code == 429: print(f"Rate limited. Waiting {timeout} seconds.") time.sleep(timeout) # 递归重试当前页,通过yield from返回结果 yield from page_helper(req, timeout=timeout, page=page, **kwargs) else: raise e else: # 先返回当前页的数据,保证每次next()先拿到当前批次 yield response_data # 如果当前页数据量等于limit,再递归拉取下一页 if len(response_data) == kwargs.get("limit", 0): yield from page_helper(req, timeout=timeout, page=page + 1, **kwargs)
修正说明
- 调整执行顺序:先
yield当前页数据,再判断是否需要递归拉取下一页,这样每次调用next()都会先返回当前批次,处理完成后才会触发下一页的拉取。 - 修复异常逻辑:通过
hasattr(e, 'response')安全判断异常对象的响应属性,同时用整数429匹配状态码。 - 完善参数传递:递归时显式传递
timeout参数,避免默认值覆盖自定义配置。
内容的提问来源于stack exchange,提问作者Jeroen Vermunt
相关产品推荐
相关产品推荐

