mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	Close #14963: Use an iterative algorithm in contextlib.ExitStack.__exit__ (Patch by Alon Horev)
This commit is contained in:
		
							parent
							
								
									c73e8c2830
								
							
						
					
					
						commit
						a5bd2a18ce
					
				
					 3 changed files with 29 additions and 28 deletions
				
			
		| 
						 | 
				
			
			@ -225,32 +225,21 @@ def __enter__(self):
 | 
			
		|||
        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)
 | 
			
		||||
            except:
 | 
			
		||||
                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:
 | 
			
		||||
                if cb(*exc_details):
 | 
			
		||||
                    suppressed_exc = True
 | 
			
		||||
                    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)
 | 
			
		||||
            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]
 | 
			
		||||
                if not self._exit_callbacks:
 | 
			
		||||
                    raise
 | 
			
		||||
                exc_details = new_exc_details
 | 
			
		||||
        return suppressed_exc
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -572,6 +572,12 @@ def test_exit_exception_chaining_suppress(self):
 | 
			
		|||
            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()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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?
 | 
			
		||||
===================================
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue