Skip to content

Commit

Permalink
bpo-23556: [doc] Fix inaccuracy in documentation for raise without ar…
Browse files Browse the repository at this point in the history
…gs. Improve tests for context in nested except handlers. (GH-29236)
  • Loading branch information
kinshukdua authored Jan 27, 2022
1 parent 606e496 commit 08c0ed2
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 20 deletions.
17 changes: 8 additions & 9 deletions Doc/library/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,14 @@ information on defining exceptions is available in the Python Tutorial under
Exception context
-----------------

When raising (or re-raising) an exception in an :keyword:`except` or
:keyword:`finally` clause
:attr:`__context__` is automatically set to the last exception caught; if the
new exception is not handled the traceback that is eventually displayed will
include the originating exception(s) and the final exception.

When raising a new exception (rather than using a bare ``raise`` to re-raise
the exception currently being handled), the implicit exception context can be
supplemented with an explicit cause by using :keyword:`from<raise>` with
When raising a new exception while another exception
is already being handled, the new exception's
:attr:`__context__` attribute is automatically set to the handled
exception. An exception may be handled when an :keyword:`except` or
:keyword:`finally` clause, or a :keyword:`with` statement, is used.

This implicit exception context can be
supplemented with an explicit cause by using :keyword:`!from` with
:keyword:`raise`::

raise new_exc from original_exc
Expand Down
18 changes: 10 additions & 8 deletions Doc/reference/simple_stmts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -563,10 +563,10 @@ The :keyword:`!raise` statement
.. productionlist:: python-grammar
raise_stmt: "raise" [`expression` ["from" `expression`]]

If no expressions are present, :keyword:`raise` re-raises the last exception
that was active in the current scope. If no exception is active in the current
scope, a :exc:`RuntimeError` exception is raised indicating that this is an
error.
If no expressions are present, :keyword:`raise` re-raises the
exception that is currently being handled, which is also known as the *active exception*.
If there isn't currently an active exception, a :exc:`RuntimeError` exception is raised
indicating that this is an error.

Otherwise, :keyword:`raise` evaluates the first expression as the exception
object. It must be either a subclass or an instance of :class:`BaseException`.
Expand All @@ -581,8 +581,8 @@ The :dfn:`type` of the exception is the exception instance's class, the
A traceback object is normally created automatically when an exception is raised
and attached to it as the :attr:`__traceback__` attribute, which is writable.
You can create an exception and set your own traceback in one step using the
:meth:`with_traceback` exception method (which returns the same exception
instance, with its traceback set to its argument), like so::
:meth:`~BaseException.with_traceback` exception method (which returns the
same exception instance, with its traceback set to its argument), like so::

raise Exception("foo occurred").with_traceback(tracebackobj)

Expand Down Expand Up @@ -614,8 +614,10 @@ exceptions will be printed::
File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

A similar mechanism works implicitly if an exception is raised inside an
exception handler or a :keyword:`finally` clause: the previous exception is then
A similar mechanism works implicitly if a new exception is raised when
an exception is already being handled. An exception may be handled
when an :keyword:`except` or :keyword:`finally` clause, or a
:keyword:`with` statement, is used. The previous exception is then
attached as the new exception's :attr:`__context__` attribute::

>>> try:
Expand Down
22 changes: 19 additions & 3 deletions Lib/test/test_raise.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def test_instance_context_instance_raise(self):
except:
raise OSError()
except OSError as e:
self.assertEqual(e.__context__, context)
self.assertIs(e.__context__, context)
else:
self.fail("No exception raised")

Expand All @@ -315,7 +315,7 @@ def test_class_context_instance_raise(self):
except:
raise OSError()
except OSError as e:
self.assertNotEqual(e.__context__, context)
self.assertIsNot(e.__context__, context)
self.assertIsInstance(e.__context__, context)
else:
self.fail("No exception raised")
Expand All @@ -328,7 +328,7 @@ def test_class_context_class_raise(self):
except:
raise OSError
except OSError as e:
self.assertNotEqual(e.__context__, context)
self.assertIsNot(e.__context__, context)
self.assertIsInstance(e.__context__, context)
else:
self.fail("No exception raised")
Expand Down Expand Up @@ -415,6 +415,22 @@ def test_reraise_cycle_broken(self):
except NameError as e:
self.assertIsNone(e.__context__.__context__)

def test_not_last(self):
# Context is not necessarily the last exception
context = Exception("context")
try:
raise context
except Exception:
try:
raise Exception("caught")
except Exception:
pass
try:
raise Exception("new")
except Exception as exc:
raised = exc
self.assertIs(raised.__context__, context)

def test_3118(self):
# deleting the generator caused the __context__ to be cleared
def gen():
Expand Down

0 comments on commit 08c0ed2

Please sign in to comment.