从脚本捕获标准输出?
- 2025-02-13 08:35:00
- admin 原创
- 37
问题描述:
假设有一个脚本正在执行如下操作:
# module writer.py
import sys
def write():
sys.stdout.write("foobar")
现在假设我想捕获write
函数的输出并将其存储在变量中以供进一步处理。简单的解决方案是:
# module mymodule.py
from writer import write
out = write()
print out.upper()
但这不管用。我想出了另一种解决方案,而且有效,但如果有更好的方法可以解决这个问题,请告诉我。谢谢
import sys
from cStringIO import StringIO
# setup the environment
backup = sys.stdout
# ####
sys.stdout = StringIO() # capture output
write()
out = sys.stdout.getvalue() # release output
# ####
sys.stdout.close() # close the stream
sys.stdout = backup # restore original stdout
print out.upper() # post processing
解决方案 1:
在Python 3.4 + 上,使用contextlib.redirect_stdout
上下文管理器:
from contextlib import redirect_stdout
import io
f = io.StringIO()
with redirect_stdout(f):
help(pow)
s = f.getvalue()
解决方案 2:
设置stdout
是一种合理的方法。另一种方法是将其作为另一个进程运行:
import subprocess
proc = subprocess.Popen(["python", "-c", "import writer; writer.write()"], stdout=subprocess.PIPE)
out = proc.communicate()[0]
print out.upper()
解决方案 3:
这是代码的上下文管理器版本。它产生两个值的列表;第一个是 stdout,第二个是 stderr。
import contextlib
@contextlib.contextmanager
def capture():
import sys
from cStringIO import StringIO
oldout,olderr = sys.stdout, sys.stderr
try:
out=[StringIO(), StringIO()]
sys.stdout,sys.stderr = out
yield out
finally:
sys.stdout,sys.stderr = oldout, olderr
out[0] = out[0].getvalue()
out[1] = out[1].getvalue()
with capture() as out:
print 'hi'
解决方案 4:
从 Python 3 开始,您还可以使用sys.stdout.buffer.write()
将 (已) 编码的字节字符串写入 stdout (请参阅Python 3 中的 stdout )。当您这样做时,简单的StringIO
方法不起作用,因为sys.stdout.encoding
nor都不sys.stdout.buffer
可用。
从 Python 2.6 开始,您可以使用TextIOBase
API,它包含缺少的属性:
import sys
from io import TextIOWrapper, BytesIO
# setup the environment
old_stdout = sys.stdout
sys.stdout = TextIOWrapper(BytesIO(), sys.stdout.encoding)
# do some writing (indirectly)
write("blub")
# get output
sys.stdout.seek(0) # jump to the start
out = sys.stdout.read() # read output
# restore stdout
sys.stdout.close()
sys.stdout = old_stdout
# do stuff with the output
print(out.upper())
此解决方案适用于 Python 2 >= 2.6 和 Python 3。请注意,我们sys.stdout.write()
仅接受 unicode 字符串和sys.stdout.buffer.write()
字节字符串。对于旧代码可能不是这种情况,但对于无需更改即可在 Python 2 和 3 上运行的代码通常如此。
如果您需要支持直接将字节字符串发送到 stdout 而不使用 stdout.buffer 的代码,您可以使用这种变体:
class StdoutBuffer(TextIOWrapper):
def write(self, string):
try:
return super(StdoutBuffer, self).write(string)
except TypeError:
# redirect encoded byte strings directly to buffer
return super(StdoutBuffer, self).buffer.write(string)
您不必设置缓冲区的编码 sys.stdout.encoding,但这在使用此方法测试/比较脚本输出时会有所帮助。
解决方案 5:
或者也许使用已经存在的功能...
from IPython.utils.capture import capture_output
with capture_output() as c:
print('some output')
c()
print c.stdout
解决方案 6:
这是我的原始代码的装饰器对应部分。
writer.py
保持不变:
import sys
def write():
sys.stdout.write("foobar")
mymodule.py
稍作修改:
from writer import write as _write
from decorators import capture
@capture
def write():
return _write()
out = write()
# out post processing...
这是装饰器:
def capture(f):
"""
Decorator to capture standard output
"""
def captured(*args, **kwargs):
import sys
from cStringIO import StringIO
# setup the environment
backup = sys.stdout
try:
sys.stdout = StringIO() # capture output
f(*args, **kwargs)
out = sys.stdout.getvalue() # release output
finally:
sys.stdout.close() # close the stream
sys.stdout = backup # restore original stdout
return out # captured output wrapped in a string
return captured
解决方案 7:
这是一个上下文管理器,它从@JonnyJD 的答案中获得灵感,支持将字节写入buffer
属性,同时还利用sys 的 dunder-io 引用来进一步简化。
import io
import sys
import contextlib
@contextlib.contextmanager
def capture_output():
output = {}
try:
# Redirect
sys.stdout = io.TextIOWrapper(io.BytesIO(), sys.stdout.encoding)
sys.stderr = io.TextIOWrapper(io.BytesIO(), sys.stderr.encoding)
yield output
finally:
# Read
sys.stdout.seek(0)
sys.stderr.seek(0)
output['stdout'] = sys.stdout.read()
output['stderr'] = sys.stderr.read()
sys.stdout.close()
sys.stderr.close()
# Restore
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
with capture_output() as output:
print('foo')
sys.stderr.buffer.write(b'bar')
print('stdout: {stdout}'.format(stdout=output['stdout']))
print('stderr: {stderr}'.format(stderr=output['stderr']))
输出为:
stdout: foo
stderr: bar
解决方案 8:
这里的问题(如何重定向输出的示例,而不是部分tee
)用于os.dup2
在操作系统级别重定向流。这很好,因为它也适用于您从程序中生成的命令。
解决方案 9:
我认为您应该看看以下四个对象:
from test.test_support import captured_stdout, captured_output, \n captured_stderr, captured_stdin
例子:
from writer import write
with captured_stdout() as stdout:
write()
print stdout.getvalue().upper()
UPD:正如 Eric 在评论中所说,不应该直接使用它们,所以我复制并粘贴了它。
# Code from test.test_support:
import contextlib
import sys
@contextlib.contextmanager
def captured_output(stream_name):
"""Return a context manager used by captured_stdout and captured_stdin
that temporarily replaces the sys stream *stream_name* with a StringIO."""
import StringIO
orig_stdout = getattr(sys, stream_name)
setattr(sys, stream_name, StringIO.StringIO())
try:
yield getattr(sys, stream_name)
finally:
setattr(sys, stream_name, orig_stdout)
def captured_stdout():
"""Capture the output of sys.stdout:
with captured_stdout() as s:
print "hello"
self.assertEqual(s.getvalue(), "hello")
"""
return captured_output("stdout")
def captured_stderr():
return captured_output("stderr")
def captured_stdin():
return captured_output("stdin")
解决方案 10:
我喜欢 contextmanager 解决方案,但是如果您需要使用打开文件和 fileno 支持存储的缓冲区,您可以做这样的事情。
import six
from six.moves import StringIO
class FileWriteStore(object):
def __init__(self, file_):
self.__file__ = file_
self.__buff__ = StringIO()
def __getattribute__(self, name):
if name in {
"write", "writelines", "get_file_value", "__file__",
"__buff__"}:
return super(FileWriteStore, self).__getattribute__(name)
return self.__file__.__getattribute__(name)
def write(self, text):
if isinstance(text, six.string_types):
try:
self.__buff__.write(text)
except:
pass
self.__file__.write(text)
def writelines(self, lines):
try:
self.__buff__.writelines(lines)
except:
pass
self.__file__.writelines(lines)
def get_file_value(self):
return self.__buff__.getvalue()
使用
import sys
sys.stdout = FileWriteStore(sys.stdout)
print "test"
buffer = sys.stdout.get_file_value()
# you don't want to print the buffer while still storing
# else it will double in size every print
sys.stdout = sys.stdout.__file__
print buffer
解决方案 11:
当第三方代码已经复制了引用时的另一种方法sys.stdout
是暂时替换write()
方法本身:
from types import MethodType
...
f = io.StringIO()
def new_write(self, data):
f.write(data)
old_write = sys.stdout.write
sys.stdout.write = MethodType(new_write, sys.stdout)
error = command.run(args)
sys.stdout.write = old_write
output = f.getvalue()
解决方案 12:
认为@arthur的评论应该存在于答案中。
使用check_output
以下方法subprocess
似乎最简单:
In [1]: import subprocess
...:
...: command = "echo 'hello world'"
...: output = subprocess.check_output(command, shell=True, encoding='utf-8')
In [2]: print(output)
hello world