Python 的 contextlib 提供了将生成器转变为上下文管理器的包装器:
from contextlib import contextmanager
@contextmanager
def gen():
yield
with gen() as cm:
...
Run Code Online (Sandbox Code Playgroud)
生成器提供了将值发送到刚刚生成的生成器的能力:
def gen():
x = yield
print(x)
g = gen()
next(g)
g.send(1234) # prints 1234 and raises a StopIteration because we have only 1 yield
Run Code Online (Sandbox Code Playgroud)
有什么办法可以同时获得这两种行为吗?我想将一个值发送到我的上下文管理器中,以便在处理__exit__. 所以像这样:
from contextlib import contextmanager
@contextmanager
def gen():
x = yield
# do something with x
with gen() as cm:
# generator already yielded from __enter__, so we can send
something.send(1234)
Run Code Online (Sandbox Code Playgroud)
我不确定这是否是一个好/合理的想法。我觉得它确实打破了一些抽象层,因为我假设上下文管理器是作为包装的生成器实现的。
如果这是一个可行的想法,我不确定something应该是什么。
a 底层的生成器@contextmanager可以通过其gen属性直接访问。由于生成器无法访问上下文管理器,因此后者必须存储在上下文之前:
from contextlib import contextmanager
@contextmanager
def gen():
print((yield)) # first yield to suspend and receive value...
yield # ... final yield to return at end of context
manager = gen() # manager must be stored to keep it accessible
with manager as value:
manager.gen.send(12)
Run Code Online (Sandbox Code Playgroud)
重要的是,生成器具有正确数量的yield点 -@contextmanager确保生成器在退出上下文后耗尽。
@contextmanager将.throw在上下文中引发异常,并且.send None完成后,底层生成器可以侦听:
@contextmanager
def gen():
# stop when we receive None on __exit__
while (value := (yield)) is not None:
print(value)
Run Code Online (Sandbox Code Playgroud)
不过,在许多情况下,将上下文管理器实现为自定义类可能更容易。这避免了使用同一通道发送/接收值和暂停/恢复上下文带来的复杂性。
class SendContext:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def send(self, value):
print("We got", value, "!!!")
with SendContext() as sc:
sc.send("Deathstar")
Run Code Online (Sandbox Code Playgroud)