cpython icon indicating copy to clipboard operation
cpython copied to clipboard

Documentation: context manager `__exit__` exception behavior wording is incomplete

Open vanschelven opened this issue 3 months ago • 4 comments

The documentation for [context manager protocol](https://docs.python.org/3/library/stdtypes.html#contextmanager.exit) currently says:

"Exceptions that occur during execution of this method will replace any exception that occurred in the body of the with statement."

This phrasing suggests that the original exception is discarded. In practice (since Python 3.0, when implicit exception chaining was introduced), an exception raised inside __exit__ becomes the one that propagates, but the original exception is not lost: it is attached as the new exception’s __context__.

The effect is that tracebacks show:

Traceback (most recent call last):
  ...
During handling of the above exception, another exception occurred:
  ...

So the original error remains visible to users and available in the exception object, rather than being fully replaced.

Suggested improvement

Change the sentence to something like:

"Exceptions that occur during execution of this method replace the exception from the body of the with statement, with the original preserved as context."

Linked PRs

  • gh-140169
  • gh-142792
  • gh-142793

vanschelven avatar Sep 25 '25 09:09 vanschelven

Exceptions that occur during execution of this method will replace any exception that occurred in the body of the with statement

This is actually correct though. The exception is replaced:

>>> class A:
...     def __enter__(self): return self
...     def __exit__(self, exc_type, *a):
...         print(exc_type)
...         raise ValueError("oh no")
...
>>> try:
...     with A():
...         1/0
... except ZeroDivisionError:
...     assert False, "nope"
...
<class 'ZeroDivisionError'>
Traceback (most recent call last):
  File "<python-input-42>", line 3, in <module>
    raise ZeroDivisionError() from None
ZeroDivisionError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<python-input-42>", line 2, in <module>
    with A():
         ~^^
  File "<python-input-36>", line 5, in __exit__
    raise ValueError("oh no")
ValueError: oh no

In the above example, the assertion error will never be raised so the exception is indeed replaced (it doesn't say anything about being lost). The fact that the old exception is now accessible in __context__ is already documented in https://docs.python.org/3/library/exceptions.html#BaseException.context (it mentions with, but this is not the only possibility; there are other cases when an exception replaces another and updates its __context__).

I'm not really against adding this small note about the original exception being in the __context__ but I think this means we're documenting twice something at two different places (and I don't think we should document this in __exit__; I think this should remain documented as part of BaseException as this affects any code that "replace" exceptions).

picnixz avatar Sep 25 '25 09:09 picnixz

This is actually correct though

is already documented

I fully agree; I'm reporting from my own perspective of (initial, though not permanent) confusion about the behavior.

but I think this means we're documenting twice something at two different places

It indeed does -- whether that is a good thing or a bad thing is always dependent on the "policy" of the Python documentation, i.e. on how to ultimately evaluate the trade-off between:

  • loss of clarity because of additional verbosity
  • loss of clarity because of excessive cross-referencing

vanschelven avatar Sep 25 '25 10:09 vanschelven

I agree that the documentation is correct but not fully precise.

Perhaps we can just rephrase it as:

Exceptions that occur during execution of this method will
-replace any exception that occurred in the body of the
+be propagated on top of any exception that occurred in the body of the
with statement.

johnslavik avatar Oct 15 '25 15:10 johnslavik

I'll open a PR.

johnslavik avatar Oct 15 '25 15:10 johnslavik