Context manager for dynamic management of a stack of exit callbacks. For example: with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] # All opened files will automatically be closed at the end of # the with sta
| 552 | |
| 553 | # Inspired by discussions on http://bugs.python.org/issue13585 |
| 554 | class ExitStack(_BaseExitStack, AbstractContextManager): |
| 555 | """Context manager for dynamic management of a stack of exit callbacks. |
| 556 | |
| 557 | For example: |
| 558 | with ExitStack() as stack: |
| 559 | files = [stack.enter_context(open(fname)) for fname in filenames] |
| 560 | # All opened files will automatically be closed at the end of |
| 561 | # the with statement, even if attempts to open files later |
| 562 | # in the list raise an exception. |
| 563 | """ |
| 564 | |
| 565 | def __enter__(self): |
| 566 | return self |
| 567 | |
| 568 | def __exit__(self, *exc_details): |
| 569 | exc = exc_details[1] |
| 570 | received_exc = exc is not None |
| 571 | |
| 572 | # We manipulate the exception state so it behaves as though |
| 573 | # we were actually nesting multiple with statements |
| 574 | frame_exc = sys.exception() |
| 575 | def _fix_exception_context(new_exc, old_exc): |
| 576 | # Context may not be correct, so find the end of the chain |
| 577 | while 1: |
| 578 | exc_context = new_exc.__context__ |
| 579 | if exc_context is None or exc_context is old_exc: |
| 580 | # Context is already set correctly (see issue 20317) |
| 581 | return |
| 582 | if exc_context is frame_exc: |
| 583 | break |
| 584 | new_exc = exc_context |
| 585 | # Change the end of the chain to point to the exception |
| 586 | # we expect it to reference |
| 587 | new_exc.__context__ = old_exc |
| 588 | |
| 589 | # Callbacks are invoked in LIFO order to match the behaviour of |
| 590 | # nested context managers |
| 591 | suppressed_exc = False |
| 592 | pending_raise = False |
| 593 | while self._exit_callbacks: |
| 594 | is_sync, cb = self._exit_callbacks.pop() |
| 595 | assert is_sync |
| 596 | try: |
| 597 | if exc is None: |
| 598 | exc_details = None, None, None |
| 599 | else: |
| 600 | exc_details = type(exc), exc, exc.__traceback__ |
| 601 | if cb(*exc_details): |
| 602 | suppressed_exc = True |
| 603 | pending_raise = False |
| 604 | exc = None |
| 605 | except BaseException as new_exc: |
| 606 | # simulate the stack of exceptions by setting the context |
| 607 | _fix_exception_context(new_exc, exc) |
| 608 | pending_raise = True |
| 609 | exc = new_exc |
| 610 | |
| 611 | if pending_raise: |
no outgoing calls
searching dependent graphs…