Nick Coghlan added the comment:

As Raymond noted, we should resist the temptation to generalise this too much - 
generalisation almost always comes at the cost of making any *specific* case 
harder (consider the difference between the complexity of the "support any URL 
scheme" model in urllib and urllib2 and the relatively API simplicity of the 
higher level HTTP/HTTPS focused requests module).

I'm wary of calling it "PrintRedirect" though, as I believe that's actively 
misleading: swapping sys.stdout for something else affects more than just the 
print builtin, and if a particular print call uses the "file" parameter, then 
the redirection of sys.stdout will have no effect.

"Redirection" in general is a bit of a misnomer for what the context manager is 
doing.

So, here's a concrete API suggestion and implementation:

    def replace_stdin(stream=None, *, data=None):
        if data is not None:
            if stream is not None:
                raise ValueError("Cannot specify both stream & data")
            stream = StringIO(data)
        return ReplaceStandardStream('stdin', stream)

    def replace_stdout(stream=None):
        return ReplaceStandardStream('stdout', stream)

    def replace_stderr(stream=None):
        return ReplaceStandardStream('stderr', stream)

    class ReplaceStandardStream:
        """Context manager to temporarily replace a standard stream

        On entry, replaces the specified sys module stream attribute
        ('stdin', 'stdout' or 'stderr') with the supplied IO stream
        object.

        On exit, restores the previous value of the sys module
        attribute.

        Note: as this context manager modifies sys module attributes
        directly, it is NOT thread-safe.
        """
        def __init__(self, attr, stream):
            if attr not in ('stdin', 'stdout', 'stderr'):
                raise ValueError("{.200!r} is not a standard stream name 
(expected one of: 'stdin', 'stdout', or 'stderr'".format(attr))
            self._attr_to_replace = attr
            self._old_stream = None
            if stream is None:
                self._replacement_stream = StringIO()
            else:
                self._replacement_stream = stream

        def __enter__(self):
            if self._old_stream is not None:
                raise RuntimeError("Cannot reenter {!r}".format(self))
            self._old_stream = getattr(sys, self._attr_to_replace)
            stream = self._replacement_stream
            setattr(sys, self._attr_to_replace, stream)
            return stream

        def __exit__(self):
            stream = self._old_stream
            if stream is None:
                raise RuntimeError("Never entered {!r}".format(self))
            self._old_stream = None
            setattr(sys, self._attr_to_replace, stream)

Cross linking from the print documentation to io.replace_stdout and from the 
input documentation to io.replace_stdin may also be a good idea.

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue15805>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to