Python関数呼び出しからstdout出力をキャプチャする方法?


112

オブジェクトに対して何かを行うPythonライブラリを使用しています

do_something(my_object)

そしてそれを変更します。その間、いくつかの統計をstdoutに出力します。この情報を把握したいと思います。適切な解決策はdo_something()、関連情報を返すように変更することです。

out = do_something(my_object)

しかしdo_something()、この問題に取り組む開発者たちが現れるまでにはしばらくかかります。回避策として、私do_something()はstdoutへの書き込みを解析することを考えました。

コードの2点間のstdout出力をキャプチャするにはどうすればよいですか。

start_capturing()
do_something(my_object)
out = end_capturing()



リンクされた質問の私の答えはここにも適用されます。
Martijn Pieters

私は一度それをやろうとしました、そして私が見つけた最良の答えは次のとおりでした: stackoverflow.com/a/3113913/1330293
elyase

@elyaseリンクされた回答はエレガントなソリューションです
Pykler

この回答を参照してください。
martineau 2013年

回答:


183

このコンテキストマネージャーを試してください:

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、リストであり、コンテキストマネージャである方が間違いなく便利です)。


ありがとう!そして、高度なセクションを追加してくれてありがとう...もともとはスライス割り当てを使用して、キャプチャしたテキストをリストに貼り付けていました。その後.extend()、気がついたように、連結して使用できるように、頭の中でたたいて代わりに使用しました。:-)
キンダル

PS繰り返し使用する場合は、メソッドに呼び出しのself._stringio.truncate(0)後にを追加して、メンバーが保持しているメモリの一部を解放することをお勧めします。self.extend()__exit__()_stringio
martineau 2013年

25
すばらしい答え、ありがとう。Python 3の場合from io import StringIO、コンテキストマネージャの最初の行の代わりに使用します。
Wtower 2015年

1
これはスレッドセーフですか?do_somethingの実行中に他のスレッド/呼び出しがprint()を使用するとどうなりますか?
Derorrist、2015年

1
この回答はC共有ライブラリからの出力には機能しません。代わりにこの回答を参照してください。
craymichael

81

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に対するグローバルな副作用は、このコンテキストマネージャがライブラリコードおよびほとんどのスレッドアプリケーションでの使用に適していないことを意味することに注意してください。また、サブプロセスの出力にも影響しません。ただし、それでも多くのユーティリティスクリプトで有用なアプローチです。

このコンテキストマネージャは再入可能です。


しようとしf = io.StringIO() with redirect_stdout(f): logger = getLogger('test_logger') logger.debug('Test debug message') out = f.getvalue() self.assertEqual(out, 'DEBUG:test_logger:Test debug message')たとき。それは私にエラーを与える:AssertionError: '' != 'Test debug message'
Eziz Durdyyev

つまり、私は何か間違ったことをしたか、標準出力のログをキャッチできませんでした。
Eziz Durdyyev

@EzizDurdyyev、logger.debugデフォルトではstdoutに書き込みません。ログ呼び出しを置き換えるとprint()、メッセージが表示されます。
ForeverWintr

ええ、わかっていますが、次のようにstdoutに書き込むようにしますstream_handler = logging.StreamHandler(sys.stdout)。そのハンドラーをロガーに追加します。stdoutに書き込み、redirect_stdoutキャッチする必要がありますよね?
Eziz Durdyyev

ロガーの構成方法に問題があると思います。redirect_stdoutなしでstdoutに出力されることを確認します。含まれている場合は、コンテキストマネージャが終了するまでバッファがフラッシュされていない可能性があります。
ForeverWintr

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()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.