当生成器 Package 为充当上下文管理器时,请使用python生成器的.send()

gupuwyp2  于 2021-08-25  发布在  Java
关注(0)|答案(2)|浏览(354)

python的contextlib提供 Package 器,将生成器转换为上下文管理器:

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)

我不确定这是否是一个好/合理的想法。我觉得它确实打破了一些抽象层,因为我假设上下文管理器是作为 Package 生成器实现的。
如果这是一个可行的想法,我不确定是什么 something 应该是。

yruzcnhs

yruzcnhs1#

生成一个函数的生成器 @contextmanager 可通过its直接访问 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")
soat7uwm

soat7uwm2#

我认为这是不可能的,也许有更好的方法来实现你想要的。
在pep 344中描述了contextmanager的实现方式 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)

相关问题