如果生成器被垃圾收集,上下文管理器是否退出?

Is a context manager exited if a generator is garbage collected?

假设我们有一个像这样的简单生成器:

def generator():
    with open('example.txt', 'r') as f:
        yield f.readline()
        yield f.readline()

并像这样使用它来获得单个 element/line:

line = next(generator())

一旦临时生成器对象被垃圾回收,上下文管理器是否关闭?

我们可以测试一下。答案是肯定的,关门了。

import contextlib
@contextlib.contextmanager
def tester():
    try:
        print("enter")
        yield 42
    finally:
        print("exit")
        
       
def generator():
    with tester() as m:
        yield m
        
print(next(generator()))

结果:

enter
exit
42

with 语句与 try 语句具有相同的语义,除了 with 语句本身的主体之外还有一些额外的代码执行。来自 https://docs.python.org/3/reference/compound_stmts.html#the-with-statement"

The following code:

with EXPRESSION as TARGET:
    SUITE

is semantically equivalent to:

manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not exit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        exit(manager, None, None, None)

所以我们的 yield 表达式在 with 语句隐含的 try 语句中。

来自 https://docs.python.org/3/reference/expressions.html#yieldexpr

Yield expressions are allowed anywhere in a try construct. If the generator is not resumed before it is finalized (by reaching a zero reference count or by being garbage collected), the generator-iterator’s close() method will be called, allowing any pending finally clauses to execute.

with 语句隐含的 finally 子句显式调用上下文管理器的 __exit__ 方法,该方法关闭文件。