I'm using a Python library that does something to an object
do_something(my_object)
and changes it. While doing so, it prints some statistics to stdout, and I'd like to get a grip on this information. The proper solution would be to change do_something() to return the relevant information,
out = do_something(my_object)
but it will be a while before the devs of do_something() get to this issue. As a workaround, I thought about parsing whatever do_something() writes to stdout.
How can I capture stdout output between two points in the code, e.g.,
start_capturing()
do_something(my_object)
out = end_capturing()
?
Try this context manager:
from io import StringIO
import sys
class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stdout = self._stdout
Usage:
with Capturing() as output:
do_something(my_object)
output is now a list containing the lines printed by the function call.
Advanced usage:
What may not be obvious is that this can be done more than once and the results concatenated:
with Capturing() as output:
print('hello world')
print('displays on screen')
with Capturing(output) as output: # note the constructor argument
print('hello world2')
print('done')
print('output:', output)
Output:
displays on screen
done
output: ['hello world', 'hello world2']
Update: They added redirect_stdout() to contextlib in Python 3.4 (along with redirect_stderr()). So you could use io.StringIO with that to achieve a similar result (though Capturing being a list as well as a context manager is arguably more convenient).
In python >= 3.4, contextlib contains a redirect_stdout decorator. It can be used to answer your question like so:
import io
from contextlib import redirect_stdout
f = io.StringIO()
with redirect_stdout(f):
do_something(my_object)
out = f.getvalue()
From the docs:
Context manager for temporarily redirecting sys.stdout to another file
or file-like object.
This tool adds flexibility to existing functions or classes whose
output is hardwired to stdout.
For example, the output of help() normally is sent to sys.stdout. You
can capture that output in a string by redirecting the output to an
io.StringIO object:
f = io.StringIO()
with redirect_stdout(f):
help(pow)
s = f.getvalue()
To send the output of help() to a file on disk, redirect the output to
a regular file:
with open('help.txt', 'w') as f:
with redirect_stdout(f):
help(pow)
To send the output of help() to sys.stderr:
with redirect_stdout(sys.stderr):
help(pow)
Note that the global side effect on sys.stdout means that this context
manager is not suitable for use in library code and most threaded
applications. It also has no effect on the output of subprocesses.
However, it is still a useful approach for many utility scripts.
This context manager is reentrant.
Here is an async solution using file pipes.
import threading
import sys
import os
class Capturing():
def __init__(self):
self._stdout = None
self._stderr = None
self._r = None
self._w = None
self._thread = None
self._on_readline_cb = None
def _handler(self):
while not self._w.closed:
try:
while True:
line = self._r.readline()
if len(line) == 0: break
if self._on_readline_cb: self._on_readline_cb(line)
except:
break
def print(self, s, end=""):
print(s, file=self._stdout, end=end)
def on_readline(self, callback):
self._on_readline_cb = callback
def start(self):
self._stdout = sys.stdout
self._stderr = sys.stderr
r, w = os.pipe()
r, w = os.fdopen(r, 'r'), os.fdopen(w, 'w', 1)
self._r = r
self._w = w
sys.stdout = self._w
sys.stderr = self._w
self._thread = threading.Thread(target=self._handler)
self._thread.start()
def stop(self):
self._w.close()
if self._thread: self._thread.join()
self._r.close()
sys.stdout = self._stdout
sys.stderr = self._stderr
Example usage:
from Capturing import *
import time
capturing = Capturing()
def on_read(line):
# do something with the line
capturing.print("got line: "+line)
capturing.on_readline(on_read)
capturing.start()
print("hello 1")
time.sleep(1)
print("hello 2")
time.sleep(1)
print("hello 3")
capturing.stop()
Also drawing on #kindall and #ForeveWintr's answers, here's a class that accomplishes this. The main difference from previous answers is that this captures it as a string, not as a StringIO object, which is much more convenient to work with!
import io
from collections import UserString
from contextlib import redirect_stdout
class capture(UserString, str, redirect_stdout):
'''
Captures stdout (e.g., from ``print()``) as a variable.
Based on ``contextlib.redirect_stdout``, but saves the user the trouble of
defining and reading from an IO stream. Useful for testing the output of functions
that are supposed to print certain output.
'''
def __init__(self, seq='', *args, **kwargs):
self._io = io.StringIO()
UserString.__init__(self, seq=seq, *args, **kwargs)
redirect_stdout.__init__(self, self._io)
return
def __enter__(self, *args, **kwargs):
redirect_stdout.__enter__(self, *args, **kwargs)
return self
def __exit__(self, *args, **kwargs):
self.data += self._io.getvalue()
redirect_stdout.__exit__(self, *args, **kwargs)
return
def start(self):
self.__enter__()
return self
def stop(self):
self.__exit__(None, None, None)
return
Examples:
# Using with...as
with capture() as txt1:
print('Assign these lines')
print('to a variable')
# Using start()...stop()
txt2 = capture().start()
print('This works')
print('the same way')
txt2.stop()
print('Saved in txt1:')
print(txt1)
print('Saved in txt2:')
print(txt2)
This is implemented in Sciris as sc.capture().
Based on kindall and ForeverWintr's answer.
I create redirect_stdout function for Python<3.4:
import io
from contextlib import contextmanager
#contextmanager
def redirect_stdout(f):
try:
_stdout = sys.stdout
sys.stdout = f
yield
finally:
sys.stdout = _stdout
f = io.StringIO()
with redirect_stdout(f):
do_something()
out = f.getvalue()
Related
I want to capture stdout as it comes, to react every time it is written to. I've not been able to find anything like "io stream on-write listener" etc.
How can I redirect stdout live? at the moment I have
import sys
import time
from io import IOBase, StringIO
class Tee:
def __init__(self, target: IOBase):
self._stdout = sys.stdout
self.target = target
def __enter__(self):
sys.stdout = self.target
def __exit__(self, *args, **kwargs):
sys.stdout = self._stdout
copy_here.seek(0)
for line in copy_here.readlines():
print(line, end='')
copy_here.seek(0)
if __name__ == '__main__':
copy_here = StringIO()
with Tee(copy_here):
print('one')
print('two')
time.sleep(1)
print('three')
print(copy_here.getvalue())
But this causes all the print outputs to be buffered until the context is exited, finally they are printed.
Rather I want the output to be printed to stdout as it comes, at the same time as being copied to the stream.
Eventually I came up with the idea of making a wrapper stream around the actual target stream, that passes method calls on after intercepting them and printing them to stdout first.
This seems to work.
import sys
import time
from io import IOBase, StringIO
from types import SimpleNamespace
class Tee:
def __init__(self, target: IOBase):
self._stdout = sys.stdout
self.target = target
self.wrapped_target = SimpleNamespace()
for method in filter(lambda x: not x.startswith('_'), dir(sys.stdout)):
setattr(self.wrapped_target, method, self._wrapped_method(method))
def _wrapped_method(self, stdout_method):
def wrapped_method(*args, **kwargs):
getattr(self.target, stdout_method)(*args, **kwargs)
return getattr(self._stdout, stdout_method)(*args, **kwargs)
return wrapped_method
def __enter__(self):
sys.stdout = self.wrapped_target
def __exit__(self, *args, **kwargs):
sys.stdout = self._stdout
if __name__ == '__main__':
copy_here = StringIO()
with Tee(copy_here):
print('one')
print('two')
time.sleep(1)
print('three')
print(copy_here.getvalue())
But it seems such overkill for a pretty simple problem.
I have a problem with an external library that I use in my script.
I execute a function from this library, but the function prints the output directly. However, I need the output to check if there is a specific string in it.
How can I ensure that the output of the function in the external library comes in a variable so that I can make the comparisons with the string?
If you really have no other choice, you could redirect stdout when you call the library. Here's a piece of code adapted from this answer :
def some_library():
print("Should probably return instead of print.")
import sys
from io import StringIO
class redirected_stdout:
def __init__(self):
self._stdout = None
self._string_io = None
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._string_io = StringIO()
return self
def __exit__(self, type, value, traceback):
sys.stdout = self._stdout
#property
def string(self):
return self._string_io.getvalue()
with redirected_stdout() as out:
some_library()
result = out.string
print("return" in result)
# True
It would be much cleaner to modify the library, though.
You can exchange sys.stdout with your buffer temporarily, and then check the buffer.
def external_method():
print ("print something out, don't return")
class MyBuffer(object):
def __init__(self):
self.buffer = []
def write(self, *args, **kwargs):
self.buffer.append(args)
import sys
old_stdout = sys.stdout
sys.stdout = MyBuffer()
external_method()
my_buffer, sys.stdout = sys.stdout, old_stdout
print (my_buffer.buffer)
I know how to redirect print to a file.
import sys
orig_stdout = sys.stdout
f = file('out.txt', 'w')
sys.stdout = f
for i in range(2):
print ('i = ', i)
sys.stdout = orig_stdout
f.close()
I need to do the same but w/out a file: keep print output in a string list. How to do it in Py3k?
Edit: I can have 3rd party prints in a middle part, not my own prints, so code must be universal for usual "print()".
import sys
class ListStream:
def __init__(self):
self.data = []
def write(self, s):
self.data.append(s)
sys.stdout = x = ListStream()
for i in range(2):
print ('i = ', i)
sys.stdout = sys.__stdout__
print(x.data)
yields
['i = ', ' ', '0', '\n', 'i = ', ' ', '1', '\n']
Tip: You don't need to save the original sys.stdout
orig_stdout = sys.stdout
since sys.stdout can be reset with
sys.stdout = sys.__stdout__
You could also add some syntactic sugar by making ListStream a contextmanager:
import sys
class ListStream:
def __init__(self):
self.data = []
def write(self, s):
self.data.append(s)
def __enter__(self):
sys.stdout = self
return self
def __exit__(self, ext_type, exc_value, traceback):
sys.stdout = sys.__stdout__
By adding the __enter__ and __exit__ methods, you can now use ListStream in a with-statement which will automatically reset sys.stdout for you when Python exits the with-suite:
with ListStream() as x:
for i in range(2):
print ('i = ', i)
print(x.data)
Instead of rolling your own class, I think it's easiest to replace sys.stdout (which is simply a TextIOWrapper) with a StringIO instance you keep a reference to:
import sys
from io import StringIO
s = StringIO()
sys.stdout = s
print('yo')
print('this is stuff')
print('hi')
s.getvalue()
Out[38]: 'yo\nthis is stuff\nhi\n'
s.getvalue().splitlines()
Out[39]: ['yo', 'this is stuff', 'hi']
As #unutbu says, you can restore the original stdout with sys.stdout = sys.__stdout__; I particlarly like the idea of using a context manager to temporarily redirect stdout to where you want it to go.
That's something I often do when I need to build a ncurses application:
import sys
# in this wrapper class you can use a string list instead of a full string like I'm doing
class StdOutWrapper:
lines = []
def write(self,txt):
self.lines.append(txt)
# here is a method so you can get stuff out of your wrapper class
# I am rebuilding the text, but you can do whatever you want!
def get_text(self,beg,end):
return '\n'.join(self.lines)
mystdout = StdOutWrapper()
sys.stdout = mystdout
sys.stderr = mystdout
# do your stuff here that needs to be printed out in a string list
for i in range(2):
print ('i = ', i)
# you don't need to make your variable to cache the `stdout`/`stderr` as they still exist
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
it is working fine with python 3 and python 2.
I would write a function to do it for you, rather than trying to redirect stdout to a list (which I don't think could possibly work anyway, but don't quote me on that).
def lprint(text):
global string_list
try: string_list.append(text)
except NameError as e:
string_list = [text]
for i in range(2):
lprint ("i = {}".format(i))
print(string_list)
[OUT]: ["i = 0","i = 1"]
I would like (just for debugging purposes) being able to change how the print works in a context
def printing_foo(a):
print(a)
return a
print("{")
with print_prefix(" " * 4):
list(map(printing_foo, range(4)))
print("}")
would produce:
{
0
1
2
3
}
much like you can have a local context for decimals.
or do I have to write my own context sensitive print and import and use it in all modules?
You can use a context manager to capture sys.stdout, then prefix anything print() writes to it:
from contextlib import contextmanager
import sys
class Prefixer(object):
def __init__(self, prefix, orig):
self.prefix = prefix
self.orig = orig
def write(self, text):
self.orig.write(self.prefix + text)
def __getattr__(self, attr):
return getattr(self.orig, attr)
#contextmanager
def prefix_stdout(prefix):
current_out = sys.stdout
try:
sys.stdout = Prefixer(prefix, current_out)
yield
finally:
sys.stdout = current_out
and use as:
with prefix_stdout('Prefixed: '):
print('Hello world!')
but take into account that print() calls usually write data to stdout in separate chunks; the newline at the end is a separate write.
For debugging I would recommend the logging module.
Im trying to understand how to create a custom print function.
(using python 2.7)
import sys
class CustomPrint():
def __init__(self):
self.old_stdout=sys.stdout #save stdout
def write(self, text):
sys.stdout = self.old_stdout #restore normal stdout and print
print 'custom Print--->' + text
sys.stdout= self # make stdout use CustomPrint on next 'print'
# this is the line that trigers the problem
# how to avoid this??
myPrint = CustomPrint()
sys.stdout = myPrint
print 'why you make 2 lines??...'
The code above prints this to console:
>>>
custom Print--->why you make 2 lines??...
custom Print--->
>>>
and i want to print only one line:
>>>
1custom Print--->why you make 2 lines??...
>>>
But cant figure out how to make this custom print work , i understand that there's some kind of recursion that triggers the second output to the console (i use self.write , to assign stdout to self.write himself !)
how can i make this work ? or is my approach just completely wrong...
It's not recursion. What happens is your write function is called twice, once with the text you expect, second time with just '\n'. Try this:
import sys
class CustomPrint():
def __init__(self):
self.old_stdout=sys.stdout
def write(self, text):
text = text.rstrip()
if len(text) == 0: return
self.old_stdout.write('custom Print--->' + text + '\n')
def flush(self):
self.old_stdout.flush()
What I do in the above code is I add the new line character to the text passed in the first call, and make sure the second call made by the print statement, the one meant to print new line, doesn't print anything.
Now try to comment out the first two lines and see what happens:
def write(self, text):
#text = text.rstrip()
#if len(text) == 0: return
self.old_stdout.write('custom Print--->' + text + '\n')
One solution may be to use a context manager if it's localised.
#!/usr/bin/env python
from __future__ import print_function
from contextlib import contextmanager
#############################
#contextmanager
def no_stdout():
import sys
old_stdout = sys.stdout
class CustomPrint():
def __init__(self, stdout):
self.old_stdout = stdout
def write(self, text):
if len(text.rstrip()):
self.old_stdout.write('custom Print--->' + text)
sys.stdout = CustomPrint(old_stdout)
try:
yield
finally:
sys.stdout = old_stdout
#############################
print("BEFORE")
with no_stdout():
print("WHY HELLO!\n")
print("DING DONG!\n")
print("AFTER")
The above produces:
BEFORE
custom Print--->WHY HELLO!
custom Print--->DING DONG!
AFTER
The code would need tidying up esp. around what the class should do WRT setting stdout back to what it was.
How about doing from __future__ import print_function. This way you will use Python3 print function instead of print statement from Python2. Then you can redefine the print function:
def print(*args, **kwargs):
__builtins__.print("Custom--->", *args, **kwargs)
There is a catch however, you will have to start using print function.