Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork33.7k
Description
Bug report
Bug description:
The issue
I would expect that handling exceptions within acontextlib.contextmanager-created function would work in the same way as other functions and clear thesys.exception() after an error is handled.
importcontextlibimportsysdefp(msg):print(msg,repr(sys.exception()),sep=": ")defctx_gen():p("before yield")try:yieldexcept:p("during handling")p("after handling")ctx=contextlib.contextmanager(ctx_gen)withctx():1/0
The above example prints:
before yield: Noneduring handling: ZeroDivisionError('division by zero')after handling: ZeroDivisionError('division by zero')Whereas since the error was handled by theexcept: block, my expectation was:
before yield: Noneduring handling: ZeroDivisionError('division by zero')after handling: NoneJust working as designed?
From doing some digging, it seems like this is happening because the exception is still being handled by the_GeneratorContextManager.__exit__ function (added by the@contextlib.contextmanager decorator) that's driving thectx_gen generator, even after thectg_gen has handled it.
The following is a very rough approximation of how@contextlib.contextmanager drivesctx_gen:
c=ctx_gen()next(c)# __enter__()try:# code inside the with block1/0exceptExceptionase:# __exit__(typ, exc, tb) for e# throw exception into generator and expect to run to endtry:c.throw(e)exceptStopIteration:passelse:# __exit__(None, None, None)# expect to run to endtry:next(e)exceptStopIteration:pass
Running the above (including the definitions from the first code block) also prints:
before yield: Noneduring handling: ZeroDivisionError('division by zero')after handling: ZeroDivisionError('division by zero')In the above code, it's more clear that the exception is still being handled by theexcept Exception as e: block untilc.throw() returns/raises, which only happens after the generator exits. Therefore the exception is still being handled the entire timectx_gen is running all the code after the firstyield.
The fix?
Even though this behavior looks to be technically correct, it still seems unexpected and a bit of an abstraction leak.
Is this something that can be/should be fixed? Or should the behavior just be documented?
CPython versions tested on:
3.8, 3.9, 3.10, 3.11, CPython main branch
Operating systems tested on:
macOS