为什么下面两个代码的结果不同?

why the results of below two codes are different?

代码 1:

def af():
    a=65
    try:
       yield a

    finally:
        print('end')

print(af().next())

代码 2:

def af():
    a=65
    try:
       yield a

    finally:
        print('end')

g=af()
print(g.next())

代码1的结果是:

end
65

但是code2的结果是:

65
end

在片段 1 中,当 Python 执行时

print(af().next())

它通过了

      af().next()

然后生成器对象的引用计数降为0。此时Python调用生成器的close方法强制finally块和__exit__方法运行,所以 finally 块打印 end.

然后 print(af().next()) 运行 的 print 部分并打印 65.


在片段 2 中,当 Python 执行时

print(g.next())

生成器对象通过 g 变量的引用保持活动状态,因此 finally 块此时不会 运行,并且 Python 打印65.

然后,在解释器关闭期间,生成器的引用计数降为 0,并且 close 触发 finally 块,打印 end。这不能保证会发生 - Python 不能保证在解释器关闭时存活的对象会调用它们的析构函数 - 但它确实发生了。

这是生成器本身何时被清理的问题。当您不保存对生成器的引用时,一旦最后一个引用超出范围(至少在 CPython 上, "reference" 解释器,这里是合适的,因为它是引用计数),生成器是清理后,生成器主体中的 GeneratorExit 立即升高,使其到达 finally 块并打印。

在您的第一个示例中,print(af().next()) next returns 时刻,不再有对生成器的任何活动引用,因此它被清理,导致 finally 块立即执行(print 尚未调用,我们仍在获取要传递给它的参数)。

在你的第二个例子中,你在 g 中保存了对生成器的引用,所以直到 g 被销毁(在这种情况下程序退出),生成器保持准备恢复(它将在 yield 之后恢复)。 print 完成,然后程序进入程序清理结束并执行 finally 块。

请注意,不能保证像这样清理全局变量; CPython 2.7 碰巧在关闭时将所有全局变量设置为 None,这让你有这种行为,但它不是契约性的;你想要明确地 运行 或 close 生成器(如果需要,通过 contextlib.closing )以确保它真的被清理干净。