]> granicus.if.org Git - python/commitdiff
Close #14963: Use an iterative algorithm in contextlib.ExitStack.__exit__ (Patch...
authorNick Coghlan <ncoghlan@gmail.com>
Thu, 31 May 2012 14:00:38 +0000 (00:00 +1000)
committerNick Coghlan <ncoghlan@gmail.com>
Thu, 31 May 2012 14:00:38 +0000 (00:00 +1000)
Lib/contextlib.py
Lib/test/test_contextlib.py
Misc/NEWS

index ead11554cb146b6d2f22d3a2d1647f9f2da6ca2c..f5232b6a8421dbec5864083ab00210497b221541 100644 (file)
@@ -225,32 +225,21 @@ class ExitStack(object):
         return self
 
     def __exit__(self, *exc_details):
-        if not self._exit_callbacks:
-            return
-        # This looks complicated, but it is really just
-        # setting up a chain of try-expect statements to ensure
-        # that outer callbacks still get invoked even if an
-        # 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
+        # Callbacks are invoked in LIFO order to match the behaviour of
+        # nested context managers
+        suppressed_exc = False
+        while self._exit_callbacks:
+            cb = self._exit_callbacks.pop()
             try:
-                suppress_exc = _invoke_next_callback(exc_details)
+                if cb(*exc_details):
+                    suppressed_exc = True
+                    exc_details = (None, None, None)
             except:
-                suppress_exc = cb(*sys.exc_info())
-                # Check if this cb suppressed the inner exception
-                if not suppress_exc:
+                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]
+                if not self._exit_callbacks:
                     raise
-            else:
-                # Check if inner cb suppressed the original exception
-                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)
+                exc_details = new_exc_details
+        return suppressed_exc
index e5eed212960587a267ff655128aefd00769db639..efa9dcbc36f7a26ba2b959ef005dfd2bbf3ea730 100644 (file)
@@ -572,6 +572,12 @@ class TestExitStack(unittest.TestCase):
             stack.push(lambda *exc: 1/0)
             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):
         class Example(object): pass
         cm = Example()
index 0ae13d4d603ad890a763f620a606690d6b5887b3..caeed463530cf77d805ff9ecaad3fa0ab3be73db 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -7,11 +7,17 @@ What's New in Python 3.3.0 Beta 1?
 
 *Release date: TBD*
 
+Library
+-------
+
+- Issue #14963: Convert contextlib.ExitStack.__exit__ to use an iterative
+  algorithm (Patch by Alon Horev)
+
 Tests
 -----
 
 - 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?
 ===================================