(original) (raw)
changeset: 77282:c108bc96aec6 user: Nick Coghlan ncoghlan@gmail.com date: Fri Jun 01 22:48:32 2012 +1000 files: Lib/contextlib.py Lib/test/test_contextlib.py Misc/NEWS description: Close #14969: Improve the handling of exception chaining in contextlib.ExitStack diff -r 0eb63de72e96 -r c108bc96aec6 Lib/contextlib.py --- a/Lib/contextlib.py Fri Jun 01 11:48:32 2012 +0200 +++ b/Lib/contextlib.py Fri Jun 01 22:48:32 2012 +1000 @@ -225,6 +225,17 @@ return self def __exit__(self, *exc_details): + # We manipulate the exception state so it behaves as though + # we were actually nesting multiple with statements + frame_exc = sys.exc_info()[1] + def _fix_exception_context(new_exc, old_exc): + while 1: + exc_context = new_exc.__context__ + if exc_context in (None, frame_exc): + break + new_exc = exc_context + new_exc.__context__ = old_exc + # Callbacks are invoked in LIFO order to match the behaviour of # nested context managers suppressed_exc = False @@ -236,9 +247,8 @@ exc_details = (None, None, None) except: new_exc_details = sys.exc_info() - if exc_details != (None, None, None): - # simulate the stack of exceptions by setting the context - new_exc_details[1].__context__ = exc_details[1] + # simulate the stack of exceptions by setting the context + _fix_exception_context(new_exc_details[1], exc_details[1]) if not self._exit_callbacks: raise exc_details = new_exc_details diff -r 0eb63de72e96 -r c108bc96aec6 Lib/test/test_contextlib.py --- a/Lib/test/test_contextlib.py Fri Jun 01 11:48:32 2012 +0200 +++ b/Lib/test/test_contextlib.py Fri Jun 01 22:48:32 2012 +1000 @@ -505,6 +505,18 @@ def __exit__(self, *exc_details): raise self.exc + class RaiseExcWithContext: + def __init__(self, outer, inner): + self.outer = outer + self.inner = inner + def __enter__(self): + return self + def __exit__(self, *exc_details): + try: + raise self.inner + except: + raise self.outer + class SuppressExc: def __enter__(self): return self @@ -514,11 +526,10 @@ try: with RaiseExc(IndexError): - with RaiseExc(KeyError): - with RaiseExc(AttributeError): - with SuppressExc(): - with RaiseExc(ValueError): - 1 / 0 + with RaiseExcWithContext(KeyError, AttributeError): + with SuppressExc(): + with RaiseExc(ValueError): + 1 / 0 except IndexError as exc: self.assertIsInstance(exc.__context__, KeyError) self.assertIsInstance(exc.__context__.__context__, AttributeError) @@ -553,12 +564,8 @@ except IndexError as exc: self.assertIsInstance(exc.__context__, KeyError) self.assertIsInstance(exc.__context__.__context__, AttributeError) - # Inner exceptions were suppressed, but the with statement - # cleanup code adds the one from the body back in as the - # context of the exception raised by the outer callbacks - # See http://bugs.python.org/issue14969 - suite_exc = exc.__context__.__context__.__context__ - self.assertIsInstance(suite_exc, ZeroDivisionError) + # Inner exceptions were suppressed + self.assertIsNone(exc.__context__.__context__.__context__) else: self.fail("Expected IndexError, but no exception was raised") # Check the inner exceptions diff -r 0eb63de72e96 -r c108bc96aec6 Misc/NEWS --- a/Misc/NEWS Fri Jun 01 11:48:32 2012 +0200 +++ b/Misc/NEWS Fri Jun 01 22:48:32 2012 +1000 @@ -10,6 +10,8 @@ Library ------- +- Issue #14969: Better handling of exception chaining in contextlib.ExitStack + - Issue #14962: Update text coloring in IDLE shell window after changing options. Patch by Roger Serwy. /ncoghlan@gmail.com