问题
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