Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 77452fc

Browse files
committed
Close #14969: Improve the handling of exception chaining in contextlib.ExitStack
1 parent c4b78a3 commit 77452fc

3 files changed

Lines changed: 33 additions & 14 deletions

File tree

Lib/contextlib.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,17 @@ def __enter__(self):
225225
return self
226226

227227
def __exit__(self, *exc_details):
228+
# We manipulate the exception state so it behaves as though
229+
# we were actually nesting multiple with statements
230+
frame_exc = sys.exc_info()[1]
231+
def _fix_exception_context(new_exc, old_exc):
232+
while 1:
233+
exc_context = new_exc.__context__
234+
if exc_context in (None, frame_exc):
235+
break
236+
new_exc = exc_context
237+
new_exc.__context__ = old_exc
238+
228239
# Callbacks are invoked in LIFO order to match the behaviour of
229240
# nested context managers
230241
suppressed_exc = False
@@ -236,9 +247,8 @@ def __exit__(self, *exc_details):
236247
exc_details = (None, None, None)
237248
except:
238249
new_exc_details = sys.exc_info()
239-
if exc_details != (None, None, None):
240-
# simulate the stack of exceptions by setting the context
241-
new_exc_details[1].__context__ = exc_details[1]
250+
# simulate the stack of exceptions by setting the context
251+
_fix_exception_context(new_exc_details[1], exc_details[1])
242252
if not self._exit_callbacks:
243253
raise
244254
exc_details = new_exc_details

Lib/test/test_contextlib.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,18 @@ def __enter__(self):
505505
def __exit__(self, *exc_details):
506506
raise self.exc
507507

508+
class RaiseExcWithContext:
509+
def __init__(self, outer, inner):
510+
self.outer = outer
511+
self.inner = inner
512+
def __enter__(self):
513+
return self
514+
def __exit__(self, *exc_details):
515+
try:
516+
raise self.inner
517+
except:
518+
raise self.outer
519+
508520
class SuppressExc:
509521
def __enter__(self):
510522
return self
@@ -514,11 +526,10 @@ def __exit__(self, *exc_details):
514526

515527
try:
516528
with RaiseExc(IndexError):
517-
with RaiseExc(KeyError):
518-
with RaiseExc(AttributeError):
519-
with SuppressExc():
520-
with RaiseExc(ValueError):
521-
1 / 0
529+
with RaiseExcWithContext(KeyError, AttributeError):
530+
with SuppressExc():
531+
with RaiseExc(ValueError):
532+
1 / 0
522533
except IndexError as exc:
523534
self.assertIsInstance(exc.__context__, KeyError)
524535
self.assertIsInstance(exc.__context__.__context__, AttributeError)
@@ -553,12 +564,8 @@ def suppress_exc(*exc_details):
553564
except IndexError as exc:
554565
self.assertIsInstance(exc.__context__, KeyError)
555566
self.assertIsInstance(exc.__context__.__context__, AttributeError)
556-
# Inner exceptions were suppressed, but the with statement
557-
# cleanup code adds the one from the body back in as the
558-
# context of the exception raised by the outer callbacks
559-
# See http://bugs.python.org/issue14969
560-
suite_exc = exc.__context__.__context__.__context__
561-
self.assertIsInstance(suite_exc, ZeroDivisionError)
567+
# Inner exceptions were suppressed
568+
self.assertIsNone(exc.__context__.__context__.__context__)
562569
else:
563570
self.fail("Expected IndexError, but no exception was raised")
564571
# Check the inner exceptions

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ What's New in Python 3.3.0 Beta 1?
1010
Library
1111
-------
1212

13+
- Issue #14969: Better handling of exception chaining in contextlib.ExitStack
14+
1315
- Issue #14962: Update text coloring in IDLE shell window after changing
1416
options. Patch by Roger Serwy.
1517

0 commit comments

Comments
 (0)