스크립트에서 stdout을 캡처 하시겠습니까?


89

다음과 같은 스크립트가 있다고 가정합니다.

# 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

답변:


49

설정 stdout은 합리적인 방법입니다. 다른 하나는 다른 프로세스로 실행하는 것입니다.

import subprocess

proc = subprocess.Popen(["python", "-c", "import writer; writer.write()"], stdout=subprocess.PIPE)
out = proc.communicate()[0]
print out.upper()

4
check_output은 하위 프로세스에서 실행 된 명령의 출력을 직접 캡처합니다. <br> value = subprocess.check_output (command, shell = True)
Arthur

1
포맷 버전 :value = subprocess.check_output(command, shell=True)
Nae

45

다음은 코드의 컨텍스트 관리자 버전입니다. 두 값의 목록을 생성합니다. 첫 번째는 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'

이 솔루션을 좋아하십시오. 예상치 못한 오류와 같이 출력을 기대하지 않는 스트림에서 실수로 항목을 잃지 않도록 수정했습니다. 필자의 경우 capture ()는 sys.stderr 또는 sys.stdout을 매개 변수로 받아 해당 스트림 만 캡처하도록 지정할 수 있습니다.
Joshua Richardson

StringIO는 어떤 방식 으로든 유니 코드를 지원하지 않으므로 여기에 답을 통합하여 위의 ASCII가 아닌 문자를 지원하도록 만들 수 있습니다. stackoverflow.com/a/1819009/425050
mafrosis

2
최종적으로 산출 값을 수정하면 정말 오히려 이상한이다 - with capture() as out:다르게 동작합니다with capture() as out, err:
에릭

유니 코드 /stdout.buffer 지원은 io 모듈을 사용하여 도달 할 수 있습니다. 내 대답을 참조하십시오 .
JonnyJD

1
이 솔루션은 subprocess출력을 사용 하고 sys.stdout / stderr로 리디렉션 하면 중단됩니다 . 이것은 StringIO실제 파일 객체가 아니고 fileno()기능을 놓치기 때문 입니다 .
letmaik 2014

44

향후 방문자를 위해 : Python 3.4 contextlib는 컨텍스트 관리자 를 통해 이를 직접 제공합니다 ( Python contextlib 도움말 참조 ) redirect_stdout.

from contextlib import redirect_stdout
import io

f = io.StringIO()
with redirect_stdout(f):
    help(pow)
s = f.getvalue()

이것은 sys.stdout.buffer에 쓰려고 할 때 문제를 해결하지 못합니다 (바이트를 쓸 때해야하는 것처럼). StringIO에는 버퍼 속성이 없지만 TextIOWrapper에는 있습니다. @JonnyJD의 답변을 참조하십시오.
weaver

9

이것은 내 원래 코드의 데코레이터 대응 물입니다.

writer.py 동일하게 유지 :

import sys

def write():
    sys.stdout.write("foobar")

mymodule.py sligthly 수정됩니다 :

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

9

또는 이미 존재하는 기능을 사용할 수도 있습니다.

from IPython.utils.capture import capture_output

with capture_output() as c:
    print('some output')

c()

print c.stdout

7

Python 3부터는 sys.stdout.buffer.write()(이미) 인코딩 된 바이트 문자열을 stdout에 쓰는 데 사용할 수도 있습니다 ( Python 3의 stdout 참조 ). 당신이 작업을 수행 할 때 간단한 StringIO어느 쪽도이 때문에 접근이 작동하지 않습니다 sys.stdout.encodingsys.stdout.buffer사용할 수 없을 것입니다.

Python 2.6부터는 누락 된 속성이 포함 된 TextIOBaseAPI를 사용할 수 있습니다 .

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()합니다. 유니 코드 문자열 sys.stdout.buffer.write()만 허용하고 바이트 문자열 만 허용합니다. 이것은 이전 코드의 경우가 아닐 수 있지만 변경없이 Python 2 및 3에서 실행되도록 빌드 된 코드의 경우가 많습니다.

stdout.buffer를 사용하지 않고 바이트 문자열을 stdout에 직접 보내는 코드를 지원해야하는 경우 다음 변형을 사용할 수 있습니다.

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에 버퍼의 인코딩을 설정할 필요는 없지만이 방법을 사용하여 스크립트 출력을 테스트 / 비교할 때 도움이됩니다.


3

여기 의 질문 ( tee부분이 아닌 출력을 리디렉션하는 방법의 예 )은 os.dup2OS 수준에서 스트림을 리디렉션하는 데 사용 합니다. 프로그램에서 생성 한 명령에도 적용되기 때문에 좋습니다.


3

이 네 가지 개체를 살펴보아야한다고 생각합니다.

from test.test_support import captured_stdout, captured_output, \
    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")

3

나는 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

0

다음은 속성에 바이트 쓰기를 지원하는 @JonnyJD의 답변 에서 영감을 얻은 컨텍스트 관리자 이며 추가 단순화를 위해 sys의 dunder-io 참조활용 합니다.buffer

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.