80

Is it possible to temporarily redirect stdout/stderr in Python (i.e. for the duration of a method)?

Edit:

The problem with the current solutions (which I at first remembered but then forgot) is that they don't redirect; rather, they just replace the streams in their entirety. Hence, if a method has a local copy of one the variable for any reason (e.g. because the stream was passed as a parameter to something), it won't work.

Any solutions?

8
  • 2
    Redirecting stdout/stderr isn't uncommon (or, at least, not unheard of) — the answers here explain the process nicely. Commented Jul 22, 2011 at 22:08
  • 2
    @TokenMacGuy: while you should never write library code that has outputs to stderr or stdout wired-in, you can't always avoid using that kind of code. Commented Jul 22, 2011 at 22:12
  • @Mehrdad Try replacing sys.__stdout__ first thing in your code, before you import the third-party modules Commented Jul 22, 2011 at 23:33
  • @Rob: I think you missed the 2nd word in my title. :-) Commented Jul 22, 2011 at 23:35
  • @Mehrdad Nope. Replace sys.__stdout__ early in your code with your own stream-like object (i.e. implements .write()). All references to sys.stdout point to it. Have it proxy to a changeable stream, defaulting to stdout. You should then have the ability to switch the proxied stream at will. I haven't tried this; I'm thinking out loud. Commented Jul 22, 2011 at 23:42

9 Answers 9

99

You can also put the redirection logic in a contextmanager.

import os import sys class RedirectStdStreams(object): def __init__(self, stdout=None, stderr=None): self._stdout = stdout or sys.stdout self._stderr = stderr or sys.stderr def __enter__(self): self.old_stdout, self.old_stderr = sys.stdout, sys.stderr self.old_stdout.flush(); self.old_stderr.flush() sys.stdout, sys.stderr = self._stdout, self._stderr def __exit__(self, exc_type, exc_value, traceback): self._stdout.flush(); self._stderr.flush() sys.stdout = self.old_stdout sys.stderr = self.old_stderr if __name__ == '__main__': devnull = open(os.devnull, 'w') print('Fubar') with RedirectStdStreams(stdout=devnull, stderr=devnull): print("You'll never see me") print("I'm back!") 
Sign up to request clarification or add additional context in comments.

5 Comments

This answer replaces sys.stdout, sys.stderr instead of redirecting them as OP asked in the edited version of the question. See my answer that does redirect them.
@RobCowie: Style question: Is there a reason why you make self._stdout "protected" (in the Python sense), vs self.__stdout "private" (in the Python sense)?
I don't seem to ever create private double-underscore properties. I've yet to find a situation that warrants it.
@kevinarpe - there's no difference between x._bar and x.__bar except that x.__bar is harder to get at, debugging wise due to name-mangling. It doesn't actually make the variable private, so it doesn't buy you anything, but makes your debugging life more difficult. In general, stick with a single underscore unless you're dealing with multiple inheritance and have a particular reason to want dunderscore (both classes' variables need to exist? I feel like there are more elegant solutions)
perhaps that with is better as with open(os.devnull, "w") as devnull, RedirectStdStreams(stdout=devnull, stderr=devnull): so that devnull is also closed fine
42

starting from python 3.4 there is the context manager contextlib.redirect_stdout:

from contextlib import redirect_stdout with open('yourfile.txt', 'w') as f: with redirect_stdout(f): # do stuff... 

to completely silence stdout this works:

from contextlib import redirect_stdout with redirect_stdout(None): # do stuff... 

6 Comments

I just tried this with redirect_stdout(None) and it didn't work. The module producing the output is written in C++, maybe that has something to do with it.
how 'does it not work'? because it really should! see e.g. this question: stackoverflow.com/questions/49757674/…
Maybe it's just a corner case, I tried wrapping a call to caffe.Net() in with redirect_stderr(None) and still got a load of debug messaged dumped to stderr. Turns out you can disable those for the specific case of Caffe another way but I guess disabling standard output is not that easy for modules that use std::iostream under the hood.
what if one wants to redirect both stdout and stderr to the same place?
@baxx with redirect_stdout(sys.stderr): and then you could redirect stderr (or the other way round).
|
25

To solve the issue that some function might have cached sys.stdout stream as a local variable and therefore replacing the global sys.stdout won't work inside that function, you could redirect at a file descriptor level (sys.stdout.fileno()) e.g.:

from __future__ import print_function import os import sys def some_function_with_cached_sys_stdout(stdout=sys.stdout): print('cached stdout', file=stdout) with stdout_redirected(to=os.devnull), merged_stderr_stdout(): print('stdout goes to devnull') some_function_with_cached_sys_stdout() print('stderr also goes to stdout that goes to devnull', file=sys.stderr) print('stdout is back') some_function_with_cached_sys_stdout() print('stderr is back', file=sys.stderr) 

stdout_redirected() redirects all output for sys.stdout.fileno() to a given filename, file object, or file descriptor (os.devnull in the example).

stdout_redirected() and merged_stderr_stdout() are defined here.

Comments

22

I am not sure what temporary redirection means. But, you can reassign streams like this and reset it back.

temp = sys.stdout sys.stdout = sys.stderr sys.stderr = temp 

Also to write to sys.stderr within print stmts like this.

 print >> sys.stderr, "Error in atexit._run_exitfuncs:" 

Regular print will to stdout.

5 Comments

I guess I could wrap a try-finally around that; seems like it'd work, though not as pretty as I wanted. Thanks +1
This example works for those of us stuck on python 2.2. Thanks
you don't need temp. sys.__stderr__ stays as the correct object.
+1 for the cleanest and simplest solution I've seen to this problem
In Python you don't need the temp variable. You can call sys.stdout, sys.stderr = sys.stderr, sys.stdout.
15

It's possible with a decorator such as the following:

import sys def redirect_stderr_stdout(stderr=sys.stderr, stdout=sys.stdout): def wrap(f): def newf(*args, **kwargs): old_stderr, old_stdout = sys.stderr, sys.stdout sys.stderr = stderr sys.stdout = stdout try: return f(*args, **kwargs) finally: sys.stderr, sys.stdout = old_stderr, old_stdout return newf return wrap 

Use as:

@redirect_stderr_stdout(some_logging_stream, the_console): def fun(...): # whatever 

or, if you don't want to modify the source for fun, call it directly as

redirect_stderr_stdout(some_logging_stream, the_console)(fun) 

But note that this is not thread-safe.

5 Comments

+1 seems to be what I need. Just curious, why'd you do an except: raise?
@Mehrdad: forget about the except: raise thing, it was a thinko on my part. I rewrote the whole thing btw.; I always get the level of nesting in decorators wrong the first time, but it actually works now.
You know, I noticed a problem: What if a program has cached the stream objects (e.g. passed them as a parameter to a function that accepts a stream)? Then this wouldn't work. :\ Is there a way to redirect the streams themselves, rather than replacing them with new streams?
@Mehrdad: then you'd do some low-level file descriptor magic, or even fork the process and redirect the streams in the child before performing the desired code, returning the result across a pipe. There's no silver bullet here, I'm afraid.
changereturn wrap(f) to return wrap
4

Here's a context manager that I found useful. The nice things about this are that you can use it with the with statement and it also handles redirecting for child processes.

import contextlib @contextlib.contextmanager def stdchannel_redirected(stdchannel, dest_filename): """ A context manager to temporarily redirect stdout or stderr e.g.: with stdchannel_redirected(sys.stderr, os.devnull): ... """ try: oldstdchannel = os.dup(stdchannel.fileno()) dest_file = open(dest_filename, 'w') os.dup2(dest_file.fileno(), stdchannel.fileno()) yield finally: if oldstdchannel is not None: os.dup2(oldstdchannel, stdchannel.fileno()) if dest_file is not None: dest_file.close() 

The context for why I created this is at this blog post.

1 Comment

Is it possible to no redirect the stdout to a file rather to just ignore it ?
2

Raymond Hettinger shows us a better way[1]:

import sys with open(filepath + filename, "w") as f: #replace filepath & filename with f as sys.stdout: print("print this to file") #will be written to filename & -path 

After the with block the sys.stdout will be reset

[1]: http://www.youtube.com/watch?v=OSGv2VnC0go&list=PLQZM27HgcgT-6D0w6arhnGdSHDcSmQ8r3

1 Comment

-1 -- The with will not restore the initial sys.stdout. If you try to print something after those with blocks you receive a ValueError: I/O operation on closed file.. You should remove the second with and put a sys.stdout = sys.__stdout__ at the end of the first with block.
1

Look at contextlib.redirect_stdout(new_target) and contextlib.redirect_stderr(new_target). redirect_stderr is new in Python 3.5.

Comments

0

We'll use the PHP syntax of ob_start and ob_get_contents functions in python3, and redirect the input into a file.

The outputs are being stored in a file, any type of stream could be used as well.

from functools import partial output_buffer = None print_orig = print def ob_start(fname="print.txt"): global print global output_buffer print = partial(print_orig, file=output_buffer) output_buffer = open(fname, 'w') def ob_end(): global output_buffer close(output_buffer) print = print_orig def ob_get_contents(fname="print.txt"): return open(fname, 'r').read() 

Usage:

print ("Hi John") ob_start() print ("Hi John") ob_end() print (ob_get_contents().replace("Hi", "Bye")) 

Would print

Hi John Bye John

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.