osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Redirecting stdio streams with a context manager


On 29/09/17 19:06, 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?
This makes every *instance* re-entrant.
I don't really see the point, but I think this would be a contrived
example of such circumstances:

redir_file1 = redirect_stdout(f1)
redir_file2 = redirect_stdout(f2)

with redir_file1:
    # do stuff
    with redir_file2:
        # Other stuff
        # Oh no wait actually
        with redir_file1:
            # Dum dee dum dee dum
            pass


>
> If you use the context manager twice:
>
> with redirect_stdout(f1) as instance1:
>     with redirect_stdout(f2) as instance2:
>         pass
>
> the two calls will return different instances and sys.stdout will be set as
> follows:
>
> # before first call to redirect_stdout
> sys.stdout = __stdout__  # the original setting
>
> # first call __enter__
> save __stdout__ in instance1._old_targets
> set sys.stdout = f1
>
> # second call __enter__
> save f1 in instance2._old_targets
> set sys.stdout = f2
>
> # second call __exit__
> restore sys.stdout = f1
>
> # first call __exit__
> restore sys.stdout = __stdout__
>
>
> I'm not seeing why _old_targets is a list.
>
>
> Can anyone explain?
>
>