2

I want to capture all the prints and do something like return them but keep running the function. I found this method but it only returns the prints when the code is finished.

f = io.StringIO()
with redirect_stdout(f):
    # my code

return f.getvalue()

Is there any method to capture every print in real-time?

martineau
  • 119,623
  • 25
  • 170
  • 301
Yoav
  • 73
  • 4
  • Can you show example(s) of the instructions you want to "capture"? – Scott Hunter Feb 07 '22 at 20:25
  • Does this answer your question? https://stackoverflow.com/q/13250050/535275 – Scott Hunter Feb 07 '22 at 20:26
  • Try using `autoflush=True` inside print call – sudden_appearance Feb 07 '22 at 20:27
  • You could write your own file like object whose write method does what you want. Use it instead of `io.StringIO()` to temporarily replace sys.stdout. – tdelaney Feb 07 '22 at 20:31
  • example: def main(n): for x in range(n): print(x) i want instead of printing x to return it and then continue from the same position – Yoav Feb 07 '22 at 20:40
  • The "return it" part is hard. The thing calling the function would need to know how to handle these interim returns (the function would be turned into an iterator yielding the values). Which argues for simply rewriting everything. You could have your own `write` method on stdout that does work (perhaps even queuing to a different thread) but it wouldn't be doing the return + continue thing. – tdelaney Feb 07 '22 at 20:50
  • `print()` is just a function, so it could be replaced with your own that does whatever you want. The replacement function can use the original, if desired. – martineau Feb 07 '22 at 21:22

1 Answers1

1

You could write your own file-like object that processes lines of text as it sees them. In the simplest case you only need to supply a write method as shown below. The tricky part is knowing when a "print" call is done. print may call stdout.write several times to do a single print operation. In this example, I did processing whenever a newline is seen. This code does not return interim prints but does allow you to intercept the writes to stdout and process them before returning to the function that calls print.

from contextlib import redirect_stdout
import sys

real_stdout_for_test = sys.stdout

class WriteProcessor:

    def __init__(self):
        self.buf = ""

    def write(self, buf):
        # emit on each newline
        while buf:
            try:
                newline_index = buf.index("\n")
            except ValueError:
                # no newline, buffer for next call
                self.buf += buf
                break
            # get data to next newline and combine with any buffered data
            data = self.buf + buf[:newline_index + 1]
            self.buf = ""
            buf = buf[newline_index + 1:]
            # perform complex calculations... or just print with a note.
            real_stdout_for_test.write("fiddled with " + data)
            
with redirect_stdout(WriteProcessor()):
    print("hello there")
    print("a\nprint\nof\nmany\nlines")
    print("goodbye ", end="")
    print("for now")
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • I played with your code and it worked. But if I tried it without swapping `sys.stdout`, it ended up with max recursion error. This is understandable, but what confuses me is why swapping the name `sys.stdout` with `real_stdout_for_test` makes a difference. They both point to the same object, correct? Why `real_stdout_for_test.write("fiddled with " + data)` works yet `sys.stdout.write("fiddled with " + data)` does not work? – Fanchen Bao Feb 21 '23 at 19:42
  • I understand why. The clue is in the [source code](https://github.com/python/cpython/blob/3bfa608cbe0d740be7628f8155b73d6f02eec797/Lib/contextlib.py#L388) – Fanchen Bao Feb 21 '23 at 20:06
  • Right, `redirect_stdout` changed `sys.stdout` so it and `real_stdout_for_test` are different objects. – tdelaney Feb 21 '23 at 20:21