如何从Python函数调用中捕获stdout输出?

时间:2013-05-15 17:13:30

标签: python stdout capture

我正在使用一个对象做某事的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()

3 个答案:

答案 0 :(得分:139)

试试这个上下文管理器:

from cStringIO 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']

更新:他们在Python 3.4中将redirect_stdout()添加到contextlib(以及redirect_stderr())。因此,您可以使用io.StringIO来获得类似的结果(尽管Capturing是一个列表,而上下文管理器可以说更方便)。

答案 1 :(得分:56)

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

来自the docs

  

用于临时将sys.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的全局副作用意味着此上下文   manager不适合在库代码和大多数线程中使用   应用。它对子进程的输出也没有影响。   但是,它仍然是许多实用程序脚本的有用方法。

     

此上下文管理器是可重入的。

答案 2 :(得分:0)

这是一个使用文件管道的异步解决方案。

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