Generator from function prints

蓝咒 提交于 2021-02-09 11:54:08

问题


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 maintaining the prints getting yelded to the website.

def get_Checks():
    root = request.url_root

    def func():
        yield ("Inicio <br>")
        with subprocess.Popen(r"python somefile.py", stdout=subprocess.PIPE, bufsize=1,
                              universal_newlines=True) as p:
            for line in p.stdout:
                yield (line + "<br>")

    return Response(func())

I've tryed to replace the file call with the function directly but it just prints it to the console.

I really appreciate any help you can provide.


回答1:


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



回答2:


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()


来源:https://stackoverflow.com/questions/39268377/generator-from-function-prints

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!