Python 的 contextlib 提供了将生成器转变为上下文管理器的包装器:
from contextlib import contextmanager
@contextmanager
def gen():
yield
with gen() as cm:
...
生成器提供了将值发送到刚刚生成的生成器的能力:
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
有什么办法可以同时获得这两种行为吗?我想将一个值发送到我的上下文管理器中,以便在处理
__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)
我不确定这是否是一个好/合理的想法。 我觉得它确实打破了一些抽象层,因为我假设上下文管理器是作为包装的生成器实现的。
如果这是一个可行的想法,我不确定
something
应该是什么。
@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)
生成器具有正确数量的
yield
点非常重要 - @contextmanager
确保生成器在退出上下文后耗尽。
@contextmanager
将 .throw
在上下文中引发异常,并且 .send
None
完成后,底层生成器可以侦听:
@contextmanager
def gen():
# stop when we receive None on __exit__
while (value := (yield)) is not None:
print(value)
在许多情况下,将上下文管理器实现为自定义类可能更容易。这避免了使用相同通道发送/接收值和暂停/恢复上下文的复杂情况。
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")
我认为这是不可能的,并且可能有更好的方法来实现你想要的。
contextmanager的实现方式在PEP 344中关于
with
语句的描述。 contextmanager 在用户的生成器函数上调用 next
方法两次,一次在 __enter__
方法中,一次在 __exit__
方法中,两次都没有发送任何值。
你可能实现你想要的方式是粗略的(未经测试):
def create_context_manager(value_you_want_to_pass):
@contextmanager
def gen():
do_something_with(value_you_want_to_pass)
yield the_object_for_with_statement
do_something_else_with(value_you_want_to_pass)
return gen
with create_context_manager(1234) as cm:
do_something_with(cm)
@MisterMiyagi 的解决方案工作得很好。谢谢。
除此之外,第一个解决方案可能不是 Python 常识。可能很难理解为什么需要第二个
yield
并且无法获得审查批准。
通过定义生成器的第二个解决方案似乎有点太长了。
结果,我最终得到了
yield
这样的复合对象的第三种解决方案。
from contextlib import contextmanager
from dataclasses import dataclass
@dataclass
class Context:
generated: object
exec_result: int = None
@contextmanager
def gen():
ctx = Context(generated="xyz")
yield ctx
if ctx.exec_result is None:
print(" No result this time")
else:
print(" Receive result:", ctx.exec_result)
with gen() as g:
print("Acquired:", g.generated)
g.exec_result = 1000
with gen() as g:
print("Acquired:", g.generated)