Commit a5bd2a18 authored by Nick Coghlan's avatar Nick Coghlan

Close #14963: Use an iterative algorithm in contextlib.ExitStack.__exit__ (Patch by Alon Horev)

parent c73e8c28
...@@ -225,32 +225,21 @@ class ExitStack(object): ...@@ -225,32 +225,21 @@ class ExitStack(object):
return self return self
def __exit__(self, *exc_details): def __exit__(self, *exc_details):
if not self._exit_callbacks: # Callbacks are invoked in LIFO order to match the behaviour of
return # nested context managers
# This looks complicated, but it is really just suppressed_exc = False
# setting up a chain of try-expect statements to ensure while self._exit_callbacks:
# that outer callbacks still get invoked even if an cb = self._exit_callbacks.pop()
# inner one throws an exception
def _invoke_next_callback(exc_details):
# Callbacks are removed from the list in FIFO order
# but the recursion means they're invoked in LIFO order
cb = self._exit_callbacks.popleft()
if not self._exit_callbacks:
# Innermost callback is invoked directly
return cb(*exc_details)
# More callbacks left, so descend another level in the stack
try: try:
suppress_exc = _invoke_next_callback(exc_details) if cb(*exc_details):
suppressed_exc = True
exc_details = (None, None, None)
except: except:
suppress_exc = cb(*sys.exc_info()) new_exc_details = sys.exc_info()
# Check if this cb suppressed the inner exception if exc_details != (None, None, None):
if not suppress_exc: # simulate the stack of exceptions by setting the context
new_exc_details[1].__context__ = exc_details[1]
if not self._exit_callbacks:
raise raise
else: exc_details = new_exc_details
# Check if inner cb suppressed the original exception return suppressed_exc
if suppress_exc:
exc_details = (None, None, None)
suppress_exc = cb(*exc_details) or suppress_exc
return suppress_exc
# Kick off the recursive chain
return _invoke_next_callback(exc_details)
...@@ -572,6 +572,12 @@ class TestExitStack(unittest.TestCase): ...@@ -572,6 +572,12 @@ class TestExitStack(unittest.TestCase):
stack.push(lambda *exc: 1/0) stack.push(lambda *exc: 1/0)
stack.push(lambda *exc: {}[1]) stack.push(lambda *exc: {}[1])
def test_excessive_nesting(self):
# The original implementation would die with RecursionError here
with ExitStack() as stack:
for i in range(10000):
stack.callback(int)
def test_instance_bypass(self): def test_instance_bypass(self):
class Example(object): pass class Example(object): pass
cm = Example() cm = Example()
......
...@@ -7,11 +7,17 @@ What's New in Python 3.3.0 Beta 1? ...@@ -7,11 +7,17 @@ What's New in Python 3.3.0 Beta 1?
*Release date: TBD* *Release date: TBD*
Library
-------
- Issue #14963: Convert contextlib.ExitStack.__exit__ to use an iterative
algorithm (Patch by Alon Horev)
Tests Tests
----- -----
- Issue #14963 (partial): Add test cases for exception handling behaviour - Issue #14963 (partial): Add test cases for exception handling behaviour
in contextlib.ContextStack (Initial patch by Alon Horev) in contextlib.ExitStack (Initial patch by Alon Horev)
What's New in Python 3.3.0 Alpha 4? What's New in Python 3.3.0 Alpha 4?
=================================== ===================================
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment