Capturing stdout from a script in Python

suppose there is a script something like this:

# module writer.py import sys def write(): sys.stdout.write("foobar") 

Now suppose that I want to capture the output of the write function and store it in a variable for further processing. The naive solution was:

 # module mymodule.py from writer import write out = write() print out.upper() 

But that does not work. I came up with a different solution and it works, but please let me know if there is a better way to solve the problem. Thanks

 import sys from cStringIO import StringIO # setup the environment backup = sys.stdout # #### sys.stdout = StringIO() # capture output write() out = sys.stdout.getvalue() # release output # #### sys.stdout.close() # close the stream sys.stdout = backup # restore original stdout print out.upper() # post processing 
+78
python stdout sys
Feb 27 2018-11-22T00:
source share
9 answers

Setting up stdout is a smart way to do this. Another is to run it as another process:

 import subprocess proc = subprocess.Popen(["python", "-c", "import writer; writer.write()"], stdout=subprocess.PIPE) out = proc.communicate()[0] print out.upper() 
+45
Feb 27 2018-11-28T00:
source share

Here is the context version of your code. It gives a list of two values; the first is stdout, the second is stderr.

 import contextlib @contextlib.contextmanager def capture(): import sys from cStringIO import StringIO oldout,olderr = sys.stdout, sys.stderr try: out=[StringIO(), StringIO()] sys.stdout,sys.stderr = out yield out finally: sys.stdout,sys.stderr = oldout, olderr out[0] = out[0].getvalue() out[1] = out[1].getvalue() with capture() as out: print 'hi' 
+42
May 24 '12 at 18:49
source share

For future visitors: Python 3.4 contextlib provides this directly (see Python contextlib Help ) through the redirect_stdout context manager:

 from contextlib import redirect_stdout import io f = io.StringIO() with redirect_stdout(f): help(pow) s = f.getvalue() 
+32
Nov 04 '16 at 7:25
source share

Or maybe use functionality that already exists ...

 from IPython.utils.capture import capture_output with capture_output() as c: print('some output') c() print c.stdout 
+10
Feb 25 '16 at 10:18
source share

This is a copy of the decorator of my source code.

writer.py remains unchanged:

 import sys def write(): sys.stdout.write("foobar") 

mymodule.py sligthly gets the changes:

 from writer import write as _write from decorators import capture @capture def write(): return _write() out = write() # out post processing... 

And here is the decorator:

 def capture(f): """ Decorator to capture standard output """ def captured(*args, **kwargs): import sys from cStringIO import StringIO # setup the environment backup = sys.stdout try: sys.stdout = StringIO() # capture output f(*args, **kwargs) out = sys.stdout.getvalue() # release output finally: sys.stdout.close() # close the stream sys.stdout = backup # restore original stdout return out # captured output wrapped in a string return captured 
+9
Mar 16 2018-11-11T00:
source share

Starting in Python 3, you can also use sys.stdout.buffer.write() to write (already) encoded byte strings to standard output (see standard output in Python 3 ). When you do this, the simple StringIO approach StringIO not work, because neither sys.stdout.encoding nor sys.stdout.buffer will be available.

Starting with Python 2.6, you can use the TextIOBase API , which includes missing attributes:

 import sys from io import TextIOWrapper, BytesIO # setup the environment old_stdout = sys.stdout sys.stdout = TextIOWrapper(BytesIO(), sys.stdout.encoding) # do some writing (indirectly) write("blub") # get output sys.stdout.seek(0) # jump to the start out = sys.stdout.read() # read output # restore stdout sys.stdout.close() sys.stdout = old_stdout # do stuff with the output print(out.upper()) 

This solution works for Python 2> = 2.6 and Python 3. Note that our sys.stdout.write() only accepts Unicode strings, and sys.stdout.buffer.write() only accepts byte strings. This may not apply to the old code, but often it refers to the code that was created to run on Python 2 and 3 without changes.

If you need to maintain code that sends byte strings to stdout directly, without using stdout.buffer, you can use this option:

 class StdoutBuffer(TextIOWrapper): def write(self, string): try: return super(StdoutBuffer, self).write(string) except TypeError: # redirect encoded byte strings directly to buffer return super(StdoutBuffer, self).buffer.write(string) 

You do not need to set the encoding of the sys.stdout.encoding buffer, but it helps when using this method to test / compare the output of the script.

+6
Oct 13 '13 at 11:52
source share

The question here (an example of how to redirect output, not the tee part) uses os.dup2 to redirect the stream at the OS level. This is good, because it will apply to commands that you also create from your program.

+3
Feb 27 2018-11-28T00:
source share

I think you should take a look at these four objects:

 from test.test_support import captured_stdout, captured_output, \ captured_stderr, captured_stdin 

Example:

 from writer import write with captured_stdout() as stdout: write() print stdout.getvalue().upper() 

UPD: As Eric said in a comment, they should not be used directly, so I copied and pasted it.

 # Code from test.test_support: import contextlib import sys @contextlib.contextmanager def captured_output(stream_name): """Return a context manager used by captured_stdout and captured_stdin that temporarily replaces the sys stream *stream_name* with a StringIO.""" import StringIO orig_stdout = getattr(sys, stream_name) setattr(sys, stream_name, StringIO.StringIO()) try: yield getattr(sys, stream_name) finally: setattr(sys, stream_name, orig_stdout) def captured_stdout(): """Capture the output of sys.stdout: with captured_stdout() as s: print "hello" self.assertEqual(s.getvalue(), "hello") """ return captured_output("stdout") def captured_stderr(): return captured_output("stderr") def captured_stdin(): return captured_output("stdin") 
+3
Sep 17 '13 at 8:14
source share

I like the contextmanager solution, however, if you need a buffer stored with an open file and fileno support, you can do something like this.

 import six from six.moves import StringIO class FileWriteStore(object): def __init__(self, file_): self.__file__ = file_ self.__buff__ = StringIO() def __getattribute__(self, name): if name in { "write", "writelines", "get_file_value", "__file__", "__buff__"}: return super(FileWriteStore, self).__getattribute__(name) return self.__file__.__getattribute__(name) def write(self, text): if isinstance(text, six.string_types): try: self.__buff__.write(text) except: pass self.__file__.write(text) def writelines(self, lines): try: self.__buff__.writelines(lines) except: pass self.__file__.writelines(lines) def get_file_value(self): return self.__buff__.getvalue() 

using

 import sys sys.stdout = FileWriteStore(sys.stdout) print "test" buffer = sys.stdout.get_file_value() # you don't want to print the buffer while still storing # else it will double in size every print sys.stdout = sys.stdout.__file__ print buffer 
+3
Oct 11 '16 at 15:24
source share



All Articles