问题:如何从Python函数调用中捕获标准输出?
我正在使用对对象执行某些操作的Python库
do_something(my_object)并更改它。这样做时,它会向stdout打印一些统计信息,我希望掌握这些信息。正确的解决方案是更改do_something()以返回相关信息,
out = do_something(my_object)但是开发人员需要一段时间才能do_something()解决此问题。作为一种解决方法,我考虑过解析do_something()对stdout的任何写入。
如何捕获代码两点之间的stdout输出,例如
start_capturing()
do_something(my_object)
out = end_capturing()?
回答 0
试试这个上下文管理器:
from io import StringIO 
import sys
class Capturing(list):
    def __enter__(self):
        self._stdout = sys.stdout
        sys.stdout = self._stringio = StringIO()
        return self
    def __exit__(self, *args):
        self.extend(self._stringio.getvalue().splitlines())
        del self._stringio    # free up some memory
        sys.stdout = self._stdout用法:
with Capturing() as output:
    do_something(my_object)output 现在是一个包含函数调用打印的行的列表。
高级用法:
可能不明显的是,此操作可以执行一次以上,并将结果连接在一起:
with Capturing() as output:
    print('hello world')
print('displays on screen')
with Capturing(output) as output:  # note the constructor argument
    print('hello world2')
print('done')
print('output:', output)输出:
displays on screen                     
done                                   
output: ['hello world', 'hello world2']更新:它们已添加redirect_stdout()到contextlibPython 3.4中(以及redirect_stderr())。因此,您可以使用io.StringIO它来达到类似的结果(尽管Capturing列表和上下文管理器可能更方便)。
回答 1
在python> = 3.4中,contextlib包含一个redirect_stdout装饰器。它可以像这样回答您的问题:
import io
from contextlib import redirect_stdout
f = io.StringIO()
with redirect_stdout(f):
    do_something(my_object)
out = f.getvalue()从文档:
上下文管理器,用于临时将sys.stdout重定向到另一个文件或类似文件的对象。
该工具为现有功能或类的输出增加了灵活性,这些功能或类的输出被硬连线到stdout。
例如,通常将help()的输出发送到sys.stdout。您可以通过将输出重定向到io.StringIO对象来捕获该输出的字符串:
f = io.StringIO() with redirect_stdout(f): help(pow) s = f.getvalue()要将help()的输出发送到磁盘上的文件,请将输出重定向到常规文件:
with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow)要将help()的输出发送到sys.stderr:
with redirect_stdout(sys.stderr): help(pow)请注意,对sys.stdout的全局副作用意味着此上下文管理器不适合在库代码和大多数线程应用程序中使用。它还对子流程的输出没有影响。但是,对于许多实用程序脚本,它仍然是一种有用的方法。
该上下文管理器是可重入的。
回答 2
这是使用文件管道的异步解决方案。
import threading
import sys
import os
class Capturing():
    def __init__(self):
        self._stdout = None
        self._stderr = None
        self._r = None
        self._w = None
        self._thread = None
        self._on_readline_cb = None
    def _handler(self):
        while not self._w.closed:
            try:
                while True:
                    line = self._r.readline()
                    if len(line) == 0: break
                    if self._on_readline_cb: self._on_readline_cb(line)
            except:
                break
    def print(self, s, end=""):
        print(s, file=self._stdout, end=end)
    def on_readline(self, callback):
        self._on_readline_cb = callback
    def start(self):
        self._stdout = sys.stdout
        self._stderr = sys.stderr
        r, w = os.pipe()
        r, w = os.fdopen(r, 'r'), os.fdopen(w, 'w', 1)
        self._r = r
        self._w = w
        sys.stdout = self._w
        sys.stderr = self._w
        self._thread = threading.Thread(target=self._handler)
        self._thread.start()
    def stop(self):
        self._w.close()
        if self._thread: self._thread.join()
        self._r.close()
        sys.stdout = self._stdout
        sys.stderr = self._stderr用法示例:
from Capturing import *
import time
capturing = Capturing()
def on_read(line):
    # do something with the line
    capturing.print("got line: "+line)
capturing.on_readline(on_read)
capturing.start()
print("hello 1")
time.sleep(1)
print("hello 2")
time.sleep(1)
print("hello 3")
capturing.stop()
