@@ -651,22 +651,33 @@ managers can not only be used in multiple :keyword:`with` statements,
651651but may also be used *inside * a :keyword: `with ` statement that is already
652652using the same context manager.
653653
654- :class: `threading.RLock ` is an example of a reentrant context manager, as is
655- :func: `suppress `. Here's a toy example of reentrant use (real world
656- examples of reentrancy are more likely to occur with objects like recursive
657- locks and are likely to be far more complicated than this example)::
658-
659- >>> from contextlib import suppress
660- >>> ignore_raised_exception = suppress(ZeroDivisionError)
661- >>> with ignore_raised_exception:
662- ... with ignore_raised_exception:
663- ... 1/0
664- ... print("This line runs")
665- ... 1/0
666- ... print("This is skipped")
654+ :class: `threading.RLock ` is an example of a reentrant context manager, as are
655+ :func: `suppress ` and :func: `redirect_stdout `. Here's a very simple example of
656+ reentrant use::
657+
658+ >>> from contextlib import redirect_stdout
659+ >>> from io import StringIO
660+ >>> stream = StringIO()
661+ >>> write_to_stream = redirect_stdout(stream)
662+ >>> with write_to_stream:
663+ ... print("This is written to the stream rather than stdout")
664+ ... with write_to_stream:
665+ ... print("This is also written to the stream")
667666 ...
668- This line runs
669- >>> # The second exception is also suppressed
667+ >>> print("This is written directly to stdout")
668+ This is written directly to stdout
669+ >>> print(stream.getvalue())
670+ This is written to the stream rather than stdout
671+ This is also written to the stream
672+
673+ Real world examples of reentrancy are more likely to involve multiple
674+ functions calling each other and hence be far more complicated than this
675+ example.
676+
677+ Note also that being reentrant is *not * the same thing as being thread safe.
678+ :func: `redirect_stdout `, for example, is definitely not thread safe, as it
679+ makes a global modification to the system state by binding :data: `sys.stdout `
680+ to a different stream.
670681
671682
672683.. _reusable-cms :
@@ -681,32 +692,58 @@ reusable). These context managers support being used multiple times, but
681692will fail (or otherwise not work correctly) if the specific context manager
682693instance has already been used in a containing with statement.
683694
684- An example of a reusable context manager is :func: `redirect_stdout `::
695+ :class: `threading.Lock ` is an example of a reusable, but not reentrant,
696+ context manager (for a reentrant lock, it is necessary to use
697+ :class: `threading.RLock ` instead).
685698
686- >>> from contextlib import redirect_stdout
687- >>> from io import StringIO
688- >>> f = StringIO()
689- >>> collect_output = redirect_stdout(f)
690- >>> with collect_output:
691- ... print("Collected")
699+ Another example of a reusable, but not reentrant, context manager is
700+ :class: `ExitStack `, as it invokes *all * currently registered callbacks
701+ when leaving any with statement, regardless of where those callbacks
702+ were added::
703+
704+ >>> from contextlib import ExitStack
705+ >>> stack = ExitStack()
706+ >>> with stack:
707+ ... stack.callback(print, "Callback: from first context")
708+ ... print("Leaving first context")
692709 ...
693- >>> print("Not collected")
694- Not collected
695- >>> with collect_output:
696- ... print("Also collected")
710+ Leaving first context
711+ Callback: from first context
712+ >>> with stack:
713+ ... stack.callback(print, "Callback: from second context")
714+ ... print("Leaving second context")
697715 ...
698- >>> print(f.getvalue())
699- Collected
700- Also collected
701-
702- However, this context manager is not reentrant, so attempting to reuse it
703- within a containing with statement fails:
704-
705- >>> with collect_output:
706- ... # Nested reuse is not permitted
707- ... with collect_output:
708- ... pass
716+ Leaving second context
717+ Callback: from second context
718+ >>> with stack:
719+ ... stack.callback(print, "Callback: from outer context")
720+ ... with stack:
721+ ... stack.callback(print, "Callback: from inner context")
722+ ... print("Leaving inner context")
723+ ... print("Leaving outer context")
709724 ...
710- Traceback (most recent call last):
711- ...
712- RuntimeError: Cannot reenter <...>
725+ Leaving inner context
726+ Callback: from inner context
727+ Callback: from outer context
728+ Leaving outer context
729+
730+ As the output from the example shows, reusing a single stack object across
731+ multiple with statements works correctly, but attempting to nest them
732+ will cause the stack to be cleared at the end of the innermost with
733+ statement, which is unlikely to be desirable behaviour.
734+
735+ Using separate :class: `ExitStack ` instances instead of reusing a single
736+ instance avoids that problem::
737+
738+ >>> from contextlib import ExitStack
739+ >>> with ExitStack() as outer_stack:
740+ ... outer_stack.callback(print, "Callback: from outer context")
741+ ... with ExitStack() as inner_stack:
742+ ... inner_stack.callback(print, "Callback: from inner context")
743+ ... print("Leaving inner context")
744+ ... print("Leaving outer context")
745+ ...
746+ Leaving inner context
747+ Callback: from inner context
748+ Leaving outer context
749+ Callback: from outer context
0 commit comments