为什么下面两个代码的结果不同?
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
)以确保它真的被清理干净。
代码 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
)以确保它真的被清理干净。