如何从 Python 函数调用中捕获 stdout 输出?
- 2025-01-08 08:50:00
- admin 原创
- 116
问题描述:
我正在使用一个对对象执行某些操作的 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()
?
解决方案 1:
尝试这个上下文管理器:
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']
更新:它们在 Python 3.4 中添加到了redirect_stdout()
(contextlib
以及redirect_stderr()
)。因此,您可以使用io.StringIO
它来实现类似的结果 (尽管Capturing
作为列表以及上下文管理器可以说更方便)。
解决方案 2:
在 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 临时重定向到另一个文件或类似文件的对象。
此工具为输出硬连接到标准输出的现有函数或类增加了灵活性。
例如,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 的全局副作用意味着此上下文管理器不适合在库代码和大多数线程应用程序中使用。它对子进程的输出也没有影响。但是,对于许多实用程序脚本来说,它仍然是一种有用的方法。
该上下文管理器是可重入的。
解决方案 3:
这是一个使用文件管道的异步解决方案。
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()
解决方案 4:
根据kindall
和ForeverWintr
的回答。
我创建了redirect_stdout
以下函数Python<3.4
:
import io
from contextlib import contextmanager
@contextmanager
def redirect_stdout(f):
try:
_stdout = sys.stdout
sys.stdout = f
yield
finally:
sys.stdout = _stdout
f = io.StringIO()
with redirect_stdout(f):
do_something()
out = f.getvalue()
解决方案 5:
还借鉴了 @kindall 和 @ForeveWintr 的答案,这里有一个实现此目的的类。与以前的答案的主要区别在于,它将其捕获为字符串,而不是StringIO
对象,这使用起来更方便!
import io
from collections import UserString
from contextlib import redirect_stdout
class capture(UserString, str, redirect_stdout):
'''
Captures stdout (e.g., from ``print()``) as a variable.
Based on ``contextlib.redirect_stdout``, but saves the user the trouble of
defining and reading from an IO stream. Useful for testing the output of functions
that are supposed to print certain output.
'''
def __init__(self, seq='', *args, **kwargs):
self._io = io.StringIO()
UserString.__init__(self, seq=seq, *args, **kwargs)
redirect_stdout.__init__(self, self._io)
return
def __enter__(self, *args, **kwargs):
redirect_stdout.__enter__(self, *args, **kwargs)
return self
def __exit__(self, *args, **kwargs):
self.data += self._io.getvalue()
redirect_stdout.__exit__(self, *args, **kwargs)
return
def start(self):
self.__enter__()
return self
def stop(self):
self.__exit__(None, None, None)
return
例子:
# Using with...as
with capture() as txt1:
print('Assign these lines')
print('to a variable')
# Using start()...stop()
txt2 = capture().start()
print('This works')
print('the same way')
txt2.stop()
print('Saved in txt1:')
print(txt1)
print('Saved in txt2:')
print(txt2)
这在Sciris中实现为sc.capture()。
扫码咨询,免费领取项目管理大礼包!