我可以将标准输出重定向到某种字符串缓冲区吗?
- 2024-12-18 08:39:00
- admin 原创
- 136
问题描述:
我正在使用 pythonftplib
编写一个小型 FTP 客户端,但包中的某些函数不返回字符串输出,而是打印到stdout
。我想重定向stdout
到一个我可以从中读取输出的对象。
我知道stdout
可以重定向到任何常规文件:
stdout = open("file", "a")
但我更喜欢不使用本地驱动器的方法。
我正在寻找类似BufferedReader
Java 的东西,可以用来将缓冲区包装到流中。
解决方案 1:
from cStringIO import StringIO # Python3 use: from io import StringIO
import sys
old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()
# blah blah lots of code ...
sys.stdout = old_stdout
# examine mystdout.getvalue()
解决方案 2:
Python 3.4+中有一个contextlib.redirect_stdout()
函数:
import io
from contextlib import redirect_stdout
with io.StringIO() as buf, redirect_stdout(buf):
print('redirected')
output = buf.getvalue()
下面是一个代码示例,展示了如何在旧版本 Python 上实现它。
解决方案 3:
只需补充一下上面 Ned 的回答:您可以使用它将输出重定向到任何实现了 write(str) 方法的对象。
这可以很好地用于在 GUI 应用程序中“捕获” stdout 输出。
这是 PyQt 中的一个愚蠢的例子:
import sys
from PyQt4 import QtGui
class OutputWindow(QtGui.QPlainTextEdit):
def write(self, txt):
self.appendPlainText(str(txt))
app = QtGui.QApplication(sys.argv)
out = OutputWindow()
sys.stdout=out
out.show()
print "hello world !"
解决方案 4:
python3 的上下文管理器:
import sys
from io import StringIO
class RedirectedStdout:
def __init__(self):
self._stdout = None
self._string_io = None
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._string_io = StringIO()
return self
def __exit__(self, type, value, traceback):
sys.stdout = self._stdout
def __str__(self):
return self._string_io.getvalue()
像这样使用:
>>> with RedirectedStdout() as out:
>>> print('asdf')
>>> s = str(out)
>>> print('bsdf')
>>> print(s, out)
'asdf
' 'asdf
bsdf
'
解决方案 5:
从 Python 2.6 开始,您可以使用任何实现io.TextIOBase
API 的东西作为替代品。此解决方案还使您能够sys.stdout.buffer.write()
在 Python 3 中使用将(已)编码的字节字符串写入标准输出(请参阅Python 3 中的 stdout)。使用io.StringIO
将不起作用,因为sys.stdout.buffer
norsys.stdout.encoding
都不可用。
使用以下解决方案io.TextIOWrapper
:
import io
import sys
# Setup stdout.
old_stdout = sys.stdout
sys.stdout = io.TextIOWrapper(io.BytesIO(), sys.stdout.encoding)
# Write to stdout or stdout.buffer.
...
# Read from stdout.
sys.stdout.seek(0)
out = sys.stdout.read()
# Restore stdout.
sys.stdout.close()
sys.stdout = old_stdout
此解决方案适用于 Python 2 >= 2.6 和 Python 3。
请注意,新的sys.stdout.write()
仅接受unicode字符串,并且sys.stdout.buffer.write()
仅接受字节字符串。无需更改即可在Python 2和3上运行的代码通常使用sys.stdout.buffer
。
因此,为了同时sys.stdout.write()
接受 unicode 和字节字符串,您可以使用这个io.TextIOWrapper
子类:
class StdoutBuffer(io.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
,但这在使用此方法测试/比较脚本输出时会有所帮助。
解决方案 6:
即使发生异常,此方法也会恢复 sys.stdout。它还会获取发生异常之前的任何输出。
import io
import sys
real_stdout = sys.stdout
fake_stdout = io.BytesIO() # or perhaps io.StringIO()
try:
sys.stdout = fake_stdout
# do what you have to do to create some output
finally:
sys.stdout = real_stdout
output_string = fake_stdout.getvalue()
fake_stdout.close()
# do what you want with the output_string
在 Python 2.7.10 中测试使用 io.BytesIO()
在 Python 3.6.4 中测试使用io.StringIO()
Bob,如果您觉得修改/扩展的代码实验中的任何内容可能会变得有趣,请添加一个案例,否则请随意删除它
Ad informandum ... 在寻找一些可行的机制来“抓取”输出的过程中,通过扩展实验得出的一些评论,
numexpr.print_versions()
直接针对<stdout>
(需要清理 GUI 并将详细信息收集到调试报告中)
# THIS WORKS AS HELL: as Bob Stein proposed years ago:
# py2 SURPRISEDaBIT:
#
import io
import sys
#
real_stdout = sys.stdout # PUSH <stdout> ( store to REAL_ )
fake_stdout = io.BytesIO() # .DEF FAKE_
try: # FUSED .TRY:
sys.stdout.flush() # .flush() before
sys.stdout = fake_stdout # .SET <stdout> to use FAKE_
# ----------------------------------------- # + do what you gotta do to create some output
print 123456789 # +
import numexpr # +
QuantFX.numexpr.__version__ # + [3] via fake_stdout re-assignment, as was bufferred + "late" deferred .get_value()-read into print, to finally reach -> real_stdout
QuantFX.numexpr.print_versions() # + [4] via fake_stdout re-assignment, as was bufferred + "late" deferred .get_value()-read into print, to finally reach -> real_stdout
_ = os.system( 'echo os.system() redir-ed' )# + [1] via real_stdout + "late" deferred .get_value()-read into print, to finally reach -> real_stdout, if not ( _ = )-caught from RET-d "byteswritten" / avoided from being injected int fake_stdout
_ = os.write( sys.stderr.fileno(), # + [2] via stderr + "late" deferred .get_value()-read into print, to finally reach -> real_stdout, if not ( _ = )-caught from RET-d "byteswritten" / avoided from being injected int fake_stdout
b'os.write() redir-ed' )# *OTHERWISE, if via fake_stdout, EXC <_io.BytesIO object at 0x02C0BB10> Traceback (most recent call last):
# ----------------------------------------- # ? io.UnsupportedOperation: fileno
#''' ? YET: <_io.BytesIO object at 0x02C0BB10> has a .fileno() method listed
#>>> 'fileno' in dir( sys.stdout ) -> True ? HAS IT ADVERTISED,
#>>> pass; sys.stdout.fileno -> <built-in method fileno of _io.BytesIO object at 0x02C0BB10>
#>>> pass; sys.stdout.fileno()-> Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# io.UnsupportedOperation: fileno
# ? BUT REFUSES TO USE IT
#'''
finally: # == FINALLY:
sys.stdout.flush() # .flush() before ret'd back REAL_
sys.stdout = real_stdout # .SET <stdout> to use POP'd REAL_
sys.stdout.flush() # .flush() after ret'd back REAL_
out_string = fake_stdout.getvalue() # .GET string from FAKE_
fake_stdout.close() # <FD>.close()
# +++++++++++++++++++++++++++++++++++++ # do what you want with the out_string
#
print "
{0:}
{1:}{0:}".format( 60 * "/\\\",# "LATE" deferred print the out_string at the very end reached -> real_stdout
out_string #
)
'''
PASS'd:::::
...
os.system() redir-ed
os.write() redir-ed
////////////////////////////////////////////////////////////\n123456789
'2.5'
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Numexpr version: 2.5
NumPy version: 1.10.4
Python version: 2.7.13 |Anaconda 4.0.0 (32-bit)| (default, May 11 2017, 14:07:41) [MSC v.1500 32 bit (Intel)]
AMD/Intel CPU? True
VML available? True
VML/MKL version: Intel(R) Math Kernel Library Version 11.3.1 Product Build 20151021 for 32-bit applications
Number of threads used by default: 4 (out of 4 detected cores)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
////////////////////////////////////////////////////////////\n>>>
EXC'd :::::
...
os.system() redir-ed
////////////////////////////////////////////////////////////\n123456789
'2.5'
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Numexpr version: 2.5
NumPy version: 1.10.4
Python version: 2.7.13 |Anaconda 4.0.0 (32-bit)| (default, May 11 2017, 14:07:41) [MSC v.1500 32 bit (Intel)]
AMD/Intel CPU? True
VML available? True
VML/MKL version: Intel(R) Math Kernel Library Version 11.3.1 Product Build 20151021 for 32-bit applications
Number of threads used by default: 4 (out of 4 detected cores)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
////////////////////////////////////////////////////////////\n
Traceback (most recent call last):
File "<stdin>", line 9, in <module>
io.UnsupportedOperation: fileno
'''
解决方案 7:
在 Python3.6 中,StringIO
和cStringIO
模块已经消失,你应该使用io.StringIO
。所以你应该像第一个答案那样这样做:
import sys
from io import StringIO
old_stdout = sys.stdout
old_stderr = sys.stderr
my_stdout = sys.stdout = StringIO()
my_stderr = sys.stderr = StringIO()
# blah blah lots of code ...
sys.stdout = self.old_stdout
sys.stderr = self.old_stderr
// if you want to see the value of redirect output, be sure the std output is turn back
print(my_stdout.getvalue())
print(my_stderr.getvalue())
my_stdout.close()
my_stderr.close()
解决方案 8:
使用pipe()
并写入适当的文件描述符。
https://docs.python.org/library/os.html#file-descriptor-operations
解决方案 9:
这是对此的另一种看法。 正如文档所述contextlib.redirect_stdout
, with很棒,但对于日常使用来说仍然有点冗长。以下是如何通过子类化使其成为一行代码:io.StringIO()
`contextlib.redirect_stdout`
import sys
import io
from contextlib import redirect_stdout
class capture(redirect_stdout):
def __init__(self):
self.f = io.StringIO()
self._new_target = self.f
self._old_targets = [] # verbatim from parent class
def __enter__(self):
self._old_targets.append(getattr(sys, self._stream)) # verbatim from parent class
setattr(sys, self._stream, self._new_target) # verbatim from parent class
return self # instead of self._new_target in the parent class
def __repr__(self):
return self.f.getvalue()
由于 enter 返回 self,因此在 with 块退出后,上下文管理器对象可用。此外,由于 repr 方法,上下文管理器对象的字符串表示实际上是 stdout。所以现在你有了,
with capture() as message:
print('Hello World!')
print(str(message)=='Hello World!
') # returns True