This commit is contained in:
Stefan Krah 2012-05-31 16:03:49 +02:00
commit 1ef17954cc
3 changed files with 29 additions and 28 deletions

View File

@ -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):
except: suppressed_exc = True
suppress_exc = cb(*sys.exc_info())
# Check if this cb suppressed the inner exception
if not suppress_exc:
raise
else:
# Check if inner cb suppressed the original exception
if suppress_exc:
exc_details = (None, None, None) exc_details = (None, None, None)
suppress_exc = cb(*exc_details) or suppress_exc except:
return suppress_exc new_exc_details = sys.exc_info()
# Kick off the recursive chain if exc_details != (None, None, None):
return _invoke_next_callback(exc_details) # simulate the stack of exceptions by setting the context
new_exc_details[1].__context__ = exc_details[1]
if not self._exit_callbacks:
raise
exc_details = new_exc_details
return suppressed_exc

View File

@ -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()

View File

@ -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?
=================================== ===================================