Pythonのstdoutをある種の文字列バッファにリダイレクトできますか?


138

ftplib小さなFTPクライアントを作成するためにPythonを使用していますが、パッケージ内の一部の関数は文字列出力を返さず、に出力しstdoutます。stdout出力を読み取ることができるオブジェクトにリダイレクトしたい。

stdoutは次のようにして通常のファイルにリダイレクトできることを知っています:

stdout = open("file", "a")

しかし、私はローカルドライブを使用しない方法を好みます。

BufferedReaderバッファをストリームにラップするために使用できるJavaのようなものを探しています。


stdout = open("file", "a")それ自体は何もリダイレクトしないと思います。
Alexey

回答:


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

52
+1 。stdout常にで利用できるため、元のオブジェクトへの参照を保持する必要はありませんsys.__stdout__docs.python.org/library/sys.html#sys.__stdout__をご覧ください。
Ayman Hourieh 2009

92
まあ、それは興味深い議論です。絶対元stdoutが利用可能ですが、このように交換するときに他の誰かが標準出力に置き換えられている可能性があるため、それは、私がやったとして保存明示的に使用することをお勧めします、あなたが使用している場合、標準出力を、あなたは彼らの交換を壊したいです。
Ned Batchelder、

5
1つのスレッドでこの操作を行うと、他のスレッドの動作が変更されますか?つまり、スレッドセーフですか?
Anuvrat Parashar 2012

6
finally:ブロック内の古いstdoutを再割り当てすることを強くお勧めします。そのため、その間に例外が発生した場合も再割り当てされます。try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn 14

20
これをPython 3で使用する場合は、cStringIOをioに置き換えます。
Anthony Labarre 2014年

80

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
ありますredirect_stderrあまりにも最新のPythonに!
CMCDragonkai 2016年

このソリューションでは、try / finallyブロックを追加する必要はないと思います。
snr

35

上記の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 !"

5
Python 2.6とPyQT4で動作します。なぜ機能しないのかわからない場合、機能しているコードに反対票を投じることは奇妙に思えます!
Nicolas Lefebvre

9
flush()も追加することを忘れないでください!
ウィル

6

Python 2.6からは、 TextIOBaseは、ioモジュールのAPIを。このソリューションではsys.stdout.buffer.write()、Python 3で使用して、(すでに)エンコードされたバイト文字列をstdoutに書き込むこともできます(Python 3のstdoutを参照)。を使用してStringIOも機能しsys.stdout.encodingませsys.stdout.bufferん。どちらも使用できないためです。

TextIOWrapperを使用したソリューション:

import sys
from io import TextIOWrapper, BytesIO

# setup the environment
old_stdout = sys.stdout
sys.stdout = TextIOWrapper(BytesIO(), sys.stdout.encoding)

# do something that writes to stdout or stdout.buffer

# 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

このソリューションは、Python 2> = 2.6およびPython 3で機能します。

新しいsys.stdout.write()ものはUnicode文字列sys.stdout.buffer.write()のみを受け入れ、バイト文字列のみを受け入れることに注意してください。これは古いコードには当てはまらないかもしれませんが、多くの場合、変更なしでPython 2および3で実行するように構築されたコードには当てはまりますsys.stdout.buffer

のユニコードとバイト文字列を受け入れるわずかなバリエーションを構築できますwrite()

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に設定する必要はありませんが、この方法を使用してスクリプト出力をテスト/比較する場合に役立ちます。


この回答は、Httpieのcore.pyで使用するためにEnvironmentオブジェクトのstdoutパラメータを設定するときに役立ちました。
fragorl 2017

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


ボブ、変更/拡張されたコードの実験から何かが何らかの意味で興味深いかもしれないと感じた場合のために追加されました、そうでなければそれを自由に削除してください

広告情報...いくつかの実行可能なメカニズムを見つける際の拡張実験から、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 "\n{0:}\n{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
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
123456789
'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)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
>>>

EXC'd :::::
...
os.system() redir-ed
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
123456789
'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)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

Traceback (most recent call last):
  File "<stdin>", line 9, in <module>
io.UnsupportedOperation: fileno
'''

6

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\n' 'asdf\nbsdf\n'

4

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

1
上記のコードがどのように機能し、これが質問者の状況をどのように改善するかを説明することで、回答の質を向上させることができます。
toonice


1

これについての別の見方を示します。 文書化されているとおりの機能は優れcontextlib.redirect_stdoutio.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__は自分自身を返すので、withブロックが終了すると、コンテキストマネージャーオブジェクトを使用できるようになります。さらに、__ repr__メソッドのおかげで、コンテキストマネージャーオブジェクトの文字列表現は、実際にはstdoutです。これで、

with capture() as message:
    print('Hello World!')
print(str(message)=='Hello World!\n')  # returns True
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.