[Tutor] Yielding from a with block

Oscar Benjamin oscar.j.benjamin at gmail.com
Fri May 29 15:13:16 CEST 2015


On 29 May 2015 at 13:38, Steven D'Aprano <steve at pearwood.info> wrote:
>
> Otherwise I stand by my earlier position that you are misinterpreting
> what it means to exit a with block. Pausing it to yield is not an exit.
>
> I did an experiment, where I tried to break the finalisation
> guarantee using break, return and raise:
>
> class CM:
>     def __enter__(self):
>         return self
>     def __exit__(self, *args):
>         print("exiting")
>
> def test(n):
>     for i in range(1):
>         with CM():
>             if n == "break": break
>             if n == "return": return
>             if n == "raise": raise RuntimeError
>             yield 1
>
>
>
> Falling out the bottom of the generator finalises correctly. So do
> break, return and raise.
>
> it = test("")
> x = next(it)
> next(it, None)  # prints "exiting"
>
> it = test("break")
> next(it, None)  # prints "exiting"
>
> it = test("return")
> next(it, None)  # prints "exiting"
>
> it = test("raise")
> try: next(it)
> except: pass  # prints "exiting"
>
> Under what circumstances can execution leave the with block without the
> finalisation method __exit__ running?

The break/return should take place in the loop that controls the generator e.g.:

$ cat gencm.py

class CM:
    def __enter__(self):
        print("Entering")
        return self
    def __exit__(self, *args):
        print("Exiting")

def generator():
    with CM():
        yield 1
        yield 2
        yield 3

g = generator()

def f():
    for x in g:
        break  # Or return

f()

print("End of program")

$ python3 gencm.py
Entering
End of program
Exiting

The context manager was triggered by the end of the program. CPython
tries to call all the __del__ methods for all live objects at process
exit. Now run the same under pypy:

$ pypy --version
Python 2.7.2 (1.8+dfsg-2, Feb 19 2012, 19:18:08)
[PyPy 1.8.0 with GCC 4.6.2]

$ pypy gencm.py
Entering
End of program

The __exit__ method was not called at all under pypy. Even if I don't
keep a reference to g outside of f the __exit__ method is not called
under this version of pypy (I don't have another to test with).


--
Oscar


More information about the Tutor mailing list