Redirecting stdio streams with a context manager

Peter Otten __peter__ at web.de
Fri Sep 29 14:49:15 EDT 2017


Steve D'Aprano wrote:

> In the standard library's contextlib.py module, there is a class for
> redirecting standard I/O streams, and two public functions. The code is
> short enough to reproduce here:
> 
> # From Python 3.5
> 
> class _RedirectStream:
>     _stream = None
>     def __init__(self, new_target):
>         self._new_target = new_target
>         # We use a list of old targets to make this CM re-entrant
>         self._old_targets = []
>     def __enter__(self):
>         self._old_targets.append(getattr(sys, self._stream))
>         setattr(sys, self._stream, self._new_target)
>         return self._new_target
>     def __exit__(self, exctype, excinst, exctb):
>         setattr(sys, self._stream, self._old_targets.pop())
> 
> class redirect_stdout(_RedirectStream):
>     # docstring removed
>     _stream = "stdout"
> 
> class redirect_stderr(_RedirectStream):
>     # docstring removed
>     _stream = "stderr"
> 
> 
> 
> I don't understand the comment "We use a list of old targets to make this
> CM re-entrant". Under what circumstances will there ever be more than a
> single entry in _old_targets?
> 
> If you use the context manager twice:
> 
> with redirect_stdout(f1) as instance1:
>     with redirect_stdout(f2) as instance2:
>         pass

That's the sane approach, but I just learned that you can reuse a single 
instance:

>>> here = io.StringIO()
>>> there = io.StringIO()
>>> h = redirect_stdout(here)
>>> t = redirect_stdout(there)
>>> with h:
...     print("ham")
...     with t:
...         print("foo")
...         with h:
...             print("spam")
...             with t:
...                 print("bar")
... 
>>> here.getvalue()
'ham\nspam\n'
>>> there.getvalue()
'foo\nbar\n'





More information about the Python-list mailing list