1

I didn't find quite what I was looking for.

I want to obtain the output (stdout) from a python function in real time.

The actual problem is that I want to plot a graph (with cplot from sympy) with a progress bar in my UI. The argument verbose makes cplot output the progress to stdout.

sympy.mpmath.cplot(lambda z: z, real, imag, verbose=True)

The output would be something like:

0 of 71
1 of 71
2 of 71
...

And so on.

I want to capture line by line so I can make a progress bar. (I realize this might not be possible without implementing multithreading). I'm using python2.7 (mainly because I need libraries that aren't in python3)

So, ¿How do I achieve that?

texdditor
  • 105
  • 1
  • 1
  • 9
  • Is there a generator version of `cplot()` that yields some value instead of printing it? If not, here's [several ways to redirect stdout temporarily in Python](http://stackoverflow.com/a/22434262/4279) – jfs Oct 15 '14 at 01:07
  • With a generator version would be easier, sadly no. And for that examples... those redirect the stdout (or I think I could grasp that) but don't let me work with it in parallel. And lastly they're intended for python3. – texdditor Oct 15 '14 at 15:31
  • 1. they do "let you work with it in parallel" e.g., redirect stdout to a pipe and read it in another thread/process concurrently. 2. Only the very first example is for Python 3 and it is followed immediately by the code that does the same on older versions. – jfs Oct 15 '14 at 19:33

2 Answers2

0

You can capture stdout by monkeypatching sys.stdout. A good way to do it is using a context manager, so that it gets put back when you are done (even if the code raises an exception). If you don't use a context manager, be sure to put the original sys.stdout back using a finally block.

You'll need an object that is file-like, that takes the input and does what you want with it. Subclassing StringIO is a good start. Here's an example of a context manager that captures stdout and stderr and puts them in the result of the bound variable.

class CapturedText(object):
    pass

@contextmanager
def captured(disallow_stderr=True):
    """
    Context manager to capture the printed output of the code in the with block

    Bind the context manager to a variable using `as` and the result will be
    in the stdout property.

    >>> from tests.helpers import capture
    >>> with captured() as c:
    ...     print('hello world!')
    ...
    >>> c.stdout
    'hello world!\n'
    """
    import sys

    stdout = sys.stdout
    stderr = sys.stderr
    sys.stdout = outfile = StringIO()
    sys.stderr = errfile = StringIO()
    c = CapturedText()
    yield c
    c.stdout = outfile.getvalue()
    c.stderr = errfile.getvalue()
    sys.stdout = stdout
    sys.stderr = stderr
    if disallow_stderr and c.stderr:
        raise Exception("Got stderr output: %s" % c.stderr)

(source)

It works as shown in the docstring. You can replace StringIO() with your own class that writes the progress bar.

asmeurer
  • 86,894
  • 26
  • 169
  • 240
0

Another possibility would be to monkeypatch sympy.mpmath.visualization.print, since cplot uses print to print the output, and it uses from __future__ import print_function.

First, make sure you are using from __future__ import print_function if you aren't using Python 3, as this will otherwise be a SyntaxError.

Then something like

def progressbar_print(*args, **kwargs):
    # Take *args and convert it to a progress output
    progress(*args)
    # If you want to still print the output, do it here
    print(*args, **kwargs)

sympy.mpmath.visualization.print = progressbar_print

You might want to monkeypatch it in a custom function that puts it back, as other functions in that module might use print as well. Again, remember to do this using either a context manager or a finally block so that it gets put back even if an exception is raised.

Monkeypatching sys.stdout is definitely the more standard way of doing this, but I like this solution in that it shows that having print as a function can actually be useful.

asmeurer
  • 86,894
  • 26
  • 169
  • 240