This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: StopIteration is silenced when raised by generator within context manager
Type: behavior Stage:
Components: Interpreter Core, Library (Lib) Versions: Python 2.7
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: hannan.aha, ncoghlan
Priority: normal Keywords:

Created on 2014-04-28 21:10 by hannan.aha, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (3)
msg217421 - (view) Author: Hannan Aharonov (hannan.aha) Date: 2014-04-28 21:10
If the code that defines a context manager performs some operations on a generator that cause it to raise StopIteration, this exception is propagated to the context manager's __exit__() method and there swallowed by a try..except block. 

This can cause unexpected/unintuitive behaviour.
The following simplified example shows an infinite loop caused by this:

-------
from contextlib import contextmanager

@contextmanager
def manager(gen):
    yield
    gen.next()

emptygen = (_ for _ in ())

while True:
    with manager(emptygen):
        print "Infinite Loop"
-------

In this case, even though the generator is empty, the loop will never stop, because the generator's StopIteration exception is swallowed by the context manager. (If `gen.next()` was wrapped by try..except that reraises exception as, say, RuntimeError, the program would stop.)

I've looked at the source (here: http://hg.python.org/cpython/file/tip/Lib/contextlib.py line 67)
and I understand why this is happening: the context manager can't tell the difference between the two kinds of StopIteration - one that is raised by its own completion, and one that is raised by a call in its body. 

I don't know if this is a bug or expected behavior of nested generators. In any case, I haven't seen this issue mentioned in the documentation, and the behavior is unintuitive.

I've searched the bug tracker and found a similar issue that handled swallowing of StopIteration in the code wrapped by the context manager (http://bugs.python.org/issue1705170), and another similar issue (http://bugs.python.org/issue16610) that was closed without resolution.
msg217477 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2014-04-29 06:34
This is expected behaviour - "raise StopIteration" in a generator is equivalent to "return", except it can occur inside a called function.

The bug here is in the given context manager definition - it should be taking appropriate action if the "next" call failing is supposed to be treated as an error rather than termination of the generator.
msg217550 - (view) Author: Hannan Aharonov (hannan.aha) Date: 2014-04-29 21:05
OK. Thanks.
History
Date User Action Args
2022-04-11 14:58:02adminsetgithub: 65578
2014-04-29 21:05:27hannan.ahasetmessages: + msg217550
2014-04-29 06:34:32ncoghlansetstatus: open -> closed
resolution: not a bug
messages: + msg217477
2014-04-28 21:14:22hannan.ahasettype: behavior
2014-04-28 21:10:29hannan.ahacreate