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