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