生成器未赋值给变量时的异常行为及两种执行差异解析
生成器函数两种执行方式的差异解析
问题背景
生成器函数定义如下:
def g(n): try: yield n print("first") except BaseException as e: print(f"exception {e}") raise e finally: print("second")
两种执行场景
- 执行方式一:
>>> a = next(g(2)) exception GeneratorExit second
(注:你看到的exception是输出简化,实际会显示exception GeneratorExit)
- 执行方式二:
>>> x = g(2) >>> next(x) 2
用户疑问:为何第一种执行方式会触发异常?两种方式的核心差异是什么?
差异解析
核心区别:生成器对象的引用与生命周期
执行方式一:
a = next(g(2))- 调用
g(2)会创建一个临时生成器对象,这个对象没有被任何变量持有引用,直接传给next()调用。 next()执行到yield n,获取到值2并准备赋值给a,但此时这个临时生成器已经没有任何引用指向它。- Python的垃圾回收机制会立即回收这个无引用的生成器对象,回收时会触发生成器的关闭流程:自动抛出
GeneratorExit异常。 - 这个异常被
try-except BaseException块捕获,执行print(f"exception {e}")输出异常信息,随后raise e重新抛出——但Python会拦截这个异常,不让它传播到外部(因为这是生成器关闭的正常逻辑),同时finally块必然执行,输出second。 - 实际变量
a已经被成功赋值为2,你可以通过print(a)验证这一点,只是异常处理和finally的输出让你误以为执行异常。
- 调用
执行方式二:
x = g(2); next(x)g(2)创建的生成器对象被变量x持有引用,不会被垃圾回收。next(x)执行到yield n,返回值2后,生成器暂停在yield的位置,不会触发关闭流程,因此except和finally块都不会执行,只有yield的结果被输出。
关键细节
GeneratorExit是Python内置的用于终止生成器的异常,属于BaseException的子类,因此会被你的except BaseException捕获。- 生成器关闭时抛出的
GeneratorExit即使被重新抛出,也不会传播到生成器外部,这是Python的内置行为,目的是保证生成器能正常完成关闭流程。
内容的提问来源于stack exchange,提问作者Joan Ràfols
相关产品推荐
相关产品推荐

