bpo-23556: [doc] Fix inaccuracy in documentation for raise without args. Improve tests for context in nested except handlers. (GH-29236)

This commit is contained in:
Kinshuk Dua 2022-01-27 15:54:48 +05:30 committed by GitHub
parent 606e496dd6
commit 08c0ed2d9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 36 additions and 19 deletions

View file

@ -38,15 +38,14 @@ information on defining exceptions is available in the Python Tutorial under
Exception context Exception context
----------------- -----------------
When raising (or re-raising) an exception in an :keyword:`except` or When raising a new exception while another exception
:keyword:`finally` clause is already being handled, the new exception's
:attr:`__context__` is automatically set to the last exception caught; if the :attr:`__context__` attribute is automatically set to the handled
new exception is not handled the traceback that is eventually displayed will exception. An exception may be handled when an :keyword:`except` or
include the originating exception(s) and the final exception. :keyword:`finally` clause, or a :keyword:`with` statement, is used.
When raising a new exception (rather than using a bare ``raise`` to re-raise This implicit exception context can be
the exception currently being handled), the implicit exception context can be supplemented with an explicit cause by using :keyword:`!from` with
supplemented with an explicit cause by using :keyword:`from<raise>` with
:keyword:`raise`:: :keyword:`raise`::
raise new_exc from original_exc raise new_exc from original_exc

View file

@ -563,10 +563,10 @@ The :keyword:`!raise` statement
.. productionlist:: python-grammar .. productionlist:: python-grammar
raise_stmt: "raise" [`expression` ["from" `expression`]] raise_stmt: "raise" [`expression` ["from" `expression`]]
If no expressions are present, :keyword:`raise` re-raises the last exception If no expressions are present, :keyword:`raise` re-raises the
that was active in the current scope. If no exception is active in the current exception that is currently being handled, which is also known as the *active exception*.
scope, a :exc:`RuntimeError` exception is raised indicating that this is an If there isn't currently an active exception, a :exc:`RuntimeError` exception is raised
error. indicating that this is an error.
Otherwise, :keyword:`raise` evaluates the first expression as the exception Otherwise, :keyword:`raise` evaluates the first expression as the exception
object. It must be either a subclass or an instance of :class:`BaseException`. object. It must be either a subclass or an instance of :class:`BaseException`.
@ -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 A traceback object is normally created automatically when an exception is raised
and attached to it as the :attr:`__traceback__` attribute, which is writable. 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 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 :meth:`~BaseException.with_traceback` exception method (which returns the
instance, with its traceback set to its argument), like so:: same exception instance, with its traceback set to its argument), like so::
raise Exception("foo occurred").with_traceback(tracebackobj) raise Exception("foo occurred").with_traceback(tracebackobj)
@ -614,8 +614,10 @@ exceptions will be printed::
File "<stdin>", line 4, in <module> File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened RuntimeError: Something bad happened
A similar mechanism works implicitly if an exception is raised inside an A similar mechanism works implicitly if a new exception is raised when
exception handler or a :keyword:`finally` clause: the previous exception is then 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:: attached as the new exception's :attr:`__context__` attribute::
>>> try: >>> try:

View file

@ -303,7 +303,7 @@ class TestContext(unittest.TestCase):
except: except:
raise OSError() raise OSError()
except OSError as e: except OSError as e:
self.assertEqual(e.__context__, context) self.assertIs(e.__context__, context)
else: else:
self.fail("No exception raised") self.fail("No exception raised")
@ -315,7 +315,7 @@ class TestContext(unittest.TestCase):
except: except:
raise OSError() raise OSError()
except OSError as e: except OSError as e:
self.assertNotEqual(e.__context__, context) self.assertIsNot(e.__context__, context)
self.assertIsInstance(e.__context__, context) self.assertIsInstance(e.__context__, context)
else: else:
self.fail("No exception raised") self.fail("No exception raised")
@ -328,7 +328,7 @@ class TestContext(unittest.TestCase):
except: except:
raise OSError raise OSError
except OSError as e: except OSError as e:
self.assertNotEqual(e.__context__, context) self.assertIsNot(e.__context__, context)
self.assertIsInstance(e.__context__, context) self.assertIsInstance(e.__context__, context)
else: else:
self.fail("No exception raised") self.fail("No exception raised")
@ -415,6 +415,22 @@ class TestContext(unittest.TestCase):
except NameError as e: except NameError as e:
self.assertIsNone(e.__context__.__context__) 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): def test_3118(self):
# deleting the generator caused the __context__ to be cleared # deleting the generator caused the __context__ to be cleared
def gen(): def gen():