Skip to content Skip to sidebar Skip to footer

Generator From Function Prints

At the moment I have a little flask project that calls another python file. I'm fully aware that this way is kinda awful, and so, I want to swap it for a function call while mainta

Solution 1:

A simple way would be to temporarily change sys.stdout to a file-like object, call the function, then restore sys.stdout. The output will be available in the file-like object.

Here is a working Flask app that demonstrates the method:

import sys
from io import StringIO
from flask import Flask, request, Response
import somefile

app = Flask(__name__)

@app.route("/")
def hello():
    def func():
        yield ("Inicio <br>")

        try:
            _stdout = sys.stdout
            sys.stdout = output = StringIO()
            somefile.main()
            output.seek(0)
            for line in output:
                sys.stdout = _stdout
                yield '{}<br>'.format(line.rstrip())
                sys.stdout = output
        finally:
            sys.stdout.close()    # close the StringIO object
            sys.stdout = _stdout  # restore sys.stdout

    return Response(func())

if __name__ == "__main__":
    app.run()

Here a io.StringIO object is used to collect the standard output produced by the function, and then the lines are yielded from that object. The finally ensures that the original sys.stdout is restored afterwards. There is some additional complexity around the yield statement because yield returns control to the calling code for which stdout must be restored in case the caller also wants to print to stdout.

It's assumed that the function in somefile.py is the "main" function, and that invocation of it is guarded by a if __name__ == '__main__': test, something like this:

def main():
    for i in range(10):
        print(i)

if __name__ == '__main__':
    main()

Solution 2:

Assuming that all the printing you want to grab is done within the same module, You can monkey-patch the print function of the other module. In the example below, I use a context manager to revert the original print function after the grabbing is done.

This is mod1, the module with the misbehaving function.

def bogus_function():
    print('Hello World!')
    print('Line 2')

This is mod2, the module using mod1.bogus_function()

import io
import functools
import contextlib

import mod1

@contextlib.contextmanager
def grab_stdout(module, fd):
    def monkey_print(*args, **kwargs):
        kwargs['file'] = fd
        print(*args, **kwargs)

    setattr(module, 'print', monkey_print)
    try:
        yield
    finally:
        setattr(module, 'print', print)

def line_generator():
    fd = io.StringIO()
    with grab_stdout(mod1, fd):
        mod1.bogus_function()
    fd.seek(0)

    for line in fd:
        yield line.rstrip('\r\n') + '<br>'

for t in enumerate(line_generator()):
    print('line %d: %r' % t)

The grab_stdout() context manager redirects print calls of module to the file-like object fd. In the function line_generator(), grab_stdout() is used to store the print output of bogus_function in the StringIO object fd. The rest should be self-explanatory.

If you don't know exactly whether print is called in other modules in the call tree of the function in question, you can modify grab_stdout as follows:

import builtins
print_orig = builtins.print

@contextlib.contextmanager
def grab_stdout_global(fd):
    def monkey_print(*args, **kwargs):
        kwargs['file'] = fd
        print_orig(*args, **kwargs)

    builtins.print = monkey_print
    try:
        yield
    finally:
        builtins.print = print_orig

Post a Comment for "Generator From Function Prints"