Skip to content

Handling exceptions within a @contextmanager function doesn't clear sys.exception() #111375

Open
@pR0Ps

Description

@pR0Ps

Bug report

Bug description:

The issue

I would expect that handling exceptions within a contextlib.contextmanager-created function would work in the same way as other functions and clear the sys.exception() after an error is handled.

import contextlib import sys def p(msg): print(msg, repr(sys.exception()), sep=": ") def ctx_gen(): p("before yield") try: yield except: p("during handling") p("after handling") ctx = contextlib.contextmanager(ctx_gen) with ctx(): 1/0

The above example prints:

before yield: None during handling: ZeroDivisionError('division by zero') after handling: ZeroDivisionError('division by zero') 

Whereas since the error was handled by the except: block, my expectation was:

before yield: None during handling: ZeroDivisionError('division by zero') after handling: None 

Just 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 the ctx_gen generator, even after the ctg_gen has handled it.

The following is a very rough approximation of how @contextlib.contextmanager drives ctx_gen:

c = ctx_gen() next(c) # __enter__() try: # code inside the with block 1/0 except Exception as e: # __exit__(typ, exc, tb) for e # throw exception into generator and expect to run to end try: c.throw(e) except StopIteration: pass else: # __exit__(None, None, None) # expect to run to end try: next(e) except StopIteration: pass

Running the above (including the definitions from the first code block) also prints:

before yield: None during 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 the except Exception as e: block until c.throw() returns/raises, which only happens after the generator exits. Therefore the exception is still being handled the entire time ctx_gen is running all the code after the first yield.

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

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibPython modules in the Lib dirtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions