This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author ncoghlan
Recipients Marc.Abramowitz, alex, barry, belopolsky, brett.cannon, ncoghlan, pitrou, rhettinger, vstinner
Date 2013-07-22.02:43:57
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <>
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

        On exit, restores the previous value of the sys module

        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()
                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.
Date User Action Args
2013-07-22 02:43:59ncoghlansetrecipients: + ncoghlan, barry, brett.cannon, rhettinger, belopolsky, pitrou, vstinner, alex, Marc.Abramowitz
2013-07-22 02:43:59ncoghlansetmessageid: <>
2013-07-22 02:43:59ncoghlanlinkissue15805 messages
2013-07-22 02:43:57ncoghlancreate