python - python生成器垃圾收集
问题描述
我认为我的问题与此有关,但并不完全相同。考虑这段代码:
def countdown(n):
try:
while n > 0:
yield n
n -= 1
finally:
print('In the finally block')
def main():
for n in countdown(10):
if n == 5:
break
print('Counting... ', n)
print('Finished counting')
main()
这段代码的输出是:
Counting... 10
Counting... 9
Counting... 8
Counting... 7
Counting... 6
In the finally block
Finished counting
是否保证将在“完成计数”之前打印“在 finally 块中”行?或者这是因为 cPython 的实现细节,当引用计数达到 0 时,对象将被垃圾回收。
我也很好奇生成器的finally
块是如何countdown
执行的?例如,如果我将代码更改main
为
def main():
c = countdown(10)
for n in c:
if n == 5:
break
print('Counting... ', n)
print('Finished counting')
然后我确实看到Finished counting
之前打印过In the finally block
。垃圾收集器如何直接去finally
块?我想我一直都接受try/except/finally
它的表面价值,但在发电机的背景下思考让我三思而后行。
解决方案
正如您所料,您依赖于 CPython 引用计数的特定于实现的行为。1
事实上,如果你在 PyPy 中运行这段代码,输出通常是:
Counting... 10
Counting... 9
Counting... 8
Counting... 7
Counting... 6
Finished counting
In the finally block
如果你在交互式 PyPy 会话中运行它,最后一行可能会在很多行之后出现,甚至只有在你最终退出时才会出现。
如果您查看生成器是如何实现的,它们的方法大致如下:
def __del__(self):
self.close()
def close(self):
try:
self.raise(GeneratorExit)
except GeneratorExit:
pass
当引用计数变为零时,CPython 会立即删除对象(它还有一个垃圾收集器来分解循环引用,但这在这里不相关)。一旦生成器超出范围,它就会被删除,因此它会被关闭,因此它将 a 提升GeneratorExit
到生成器框架中并恢复它。当然,没有处理程序GeneratorExit
,因此该finally
子句被执行并且控制传递到堆栈,异常被吞没。
在使用混合垃圾收集器的 PyPy 中,直到下次 GC 决定扫描时,生成器才会被删除。在内存压力较低的交互式会话中,这可能会延迟到退出时间。但是一旦发生,同样的事情也会发生。
您可以通过显式处理来看到这一点GeneratorExit
:
def countdown(n):
try:
while n > 0:
yield n
n -= 1
except GeneratorExit:
print('Exit!')
raise
finally:
print('In the finally block')
(如果你不这样做raise
,你会得到相同的结果,只是原因略有不同。)
你可以显式地close
生成一个生成器——而且,与上面的内容不同,这是生成器类型的公共接口的一部分:
def main():
c = countdown(10)
for n in c:
if n == 5:
break
print('Counting... ', n)
c.close()
print('Finished counting')
或者,当然,您可以使用以下with
语句:
def main():
with contextlib.closing(countdown(10)) as c:
for n in c:
if n == 5:
break
print('Counting... ', n)
print('Finished counting')
1. 正如Tim Peters 的回答所指出的,您在第二个测试中还依赖于 CPython 编译器的特定于实现的行为。
推荐阅读
- javascript - Angular 9 Service中的RxJS订阅通过离开和进入包括组件的页面来增加调用
- c++ - C++ 查找 co_await 等待结果类型
- javascript - SnapshotChanges 订阅未检测到子集合项的最终删除
- sql-server - SQL Server:存储过程中的 OPENJSON 错误
- ios - 更改键盘布局出现黑线
- xpath - 仅选择一个与父名称 xpath 1.0 匹配的子项
- r - 如何在ggplot的右上角添加包含数据的geom_text标签
- r - 我如何在 R 中拟合这个线性趋势函数?
- angular - 使用 RxJS Marbles 测试 Angular 响应式表单
- python - 如何使用克里格。gstools 的 Extdrift 以插入 2D 地理空间数据