Redirecting stdio streams with a context manager

Steve D'Aprano steve+python at pearwood.info
Fri Sep 29 13:06:37 EDT 2017


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

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?


-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.




More information about the Python-list mailing list