mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +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 |         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 | ||||||
|  |  | ||||||
|  | @ -572,6 +572,12 @@ def test_exit_exception_chaining_suppress(self): | ||||||
|             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? | ||||||
| 
 | 
 | ||||||
| *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? | ||||||
| =================================== | =================================== | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Nick Coghlan
						Nick Coghlan