当生成器被包装以充当上下文管理器时,使用 Python 生成器的 .send()

Lov*_*ode 6 python generator

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应该是什么。

Mis*_*agi 6

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)