为何deque推导式中抛出的StopIteration无法被try-except捕获?
为什么生成器表达式的StopIteration无法被try-except捕获?
核心原因是列表推导式和生成器表达式的执行逻辑+PEP 479规则共同导致的:
1. 列表推导式的异常传播逻辑
列表推导式是立即执行的:当你写[next(i) for _ in range(10)]时,Python会立刻遍历推导式、调用next(i)取元素。一旦next(i)触发StopIteration,这个异常会直接向外层传播,刚好被你写的except StopIteration捕获,函数因此正常返回。
2. 生成器表达式的异常处理规则
生成器表达式是延迟执行的:(next(i) for _ in range(10))只是创建了一个生成器对象,并没有立刻执行。真正的迭代是在deque的构造函数里发生的——deque会迭代这个生成器来填充元素。
而根据Python 3.7开始默认启用的PEP 479规则:生成器内部抛出的StopIteration会被Python自动包装成RuntimeError(原异常会作为RuntimeError的__cause__属性保留)。这意味着,当生成器里的next(i)触发StopIteration时,最终向外层传播的是RuntimeError,而非原始的StopIteration,所以你的except StopIteration根本抓不到这个异常,代码自然崩溃。
3. 解决方法
如果想要避免这个问题,有几种可选方案:
- 继续使用列表推导式:它的行为完全符合你的预期,无需改动。
- 手动循环初始化deque:这种方式更直观,能精准控制异常捕获:
def ngrams(sequence, n=2): sequence = iter(sequence) ngram = deque(maxlen=n) try: for _ in range(n): ngram.append(next(sequence)) except StopIteration: return while True: yield tuple(ngram) try: ngram.append(next(sequence)) except StopIteration: return - 如果坚持用生成器表达式:需要捕获
RuntimeError并检查其__cause__,但写法不够简洁:try: ngram = deque( (next(sequence) for _ in range(n)), maxlen=n) except RuntimeError as e: if isinstance(e.__cause__, StopIteration): return raise # 其他RuntimeError需重新抛出
内容的提问来源于stack exchange,提问作者user15077986
相关产品推荐
相关产品推荐

