[Tutor] Yielding from a with block
Steven D'Aprano
steve at pearwood.info
Fri May 29 16:23:16 CEST 2015
On Thu, May 28, 2015 at 10:16:00AM +0200, Peter Otten wrote:
> Even if you limit yourself to CPython there is another effect: the order of
> execution may not meet one's expectations/requirements:
No, wait, forget everything I said in my previous post. This is
*clearly* a case where our expectations were wrong, and the context
manager guarantee is working correctly. I was confused because I was
over-thinking it and seeing something unexpected when in fact it is
working exactly as promised.
> $ cat with_in_generator.py
> import contextlib
>
> @contextlib.contextmanager
> def demo():
> print("before")
> try:
> yield
> finally:
> print("after")
>
> def gen(items="abc"):
> with demo():
> yield from items
>
> if __name__ == "__main__":
> g = gen()
> for item in g:
> print(item)
> if item == "b":
> break
> print("bye")
Since you *break* from the for-loop, the generator g never runs to
completion. Since it is still paused *inside the with block*, naturally
the context manager __exit__ doesn't run. If it did run, THAT would be a
violation of the context manager guarantee!
Now that you have broken out of the for-loop, you still have a reference
to g, and are perfectly entitled to hang on to the reference for a
while, then iterate over it again, or directly call next(g). Until such
time as you do, or explicitly call g.close(), the context manager has to
stay open.
I
Until you do so (or call g.close() to explicitly end it), it is paused
inside the with block.
There's nothing to see here. The context manager is working correctly,
and if you expect it to __exit__ while still inside the with block, it
is your expectations that are wrong.
> (in case you don't spot it: "after" should be printed before "bye")
That's mistaken. Just because you exit from the for-loop, doesn't mean
the generator is complete. Suppose you wrote a generator like this:
def gen():
yield 1
yield 2
yield 3
print("closing")
for i in gen():
if i == 2: break
print("bye")
Would you still expect it to print "closing" before "bye"?
--
Steve
More information about the Tutor
mailing list