Python 함수 호출에서 stdout 출력을 캡처하는 방법은 무엇입니까?


112

객체에 뭔가를하는 Python 라이브러리를 사용하고 있습니다.

do_something(my_object)

변경합니다. 그렇게하는 동안 몇 가지 통계를 stdout에 인쇄하고이 정보를 파악하고 싶습니다. 적절한 해결책은 do_something()관련 정보를 반환 하도록 변경 하는 것입니다 .

out = do_something(my_object)

하지만 개발자 do_something()가이 문제를 해결하기까지는 시간이 좀 걸릴 것입니다. 해결 방법 do_something()으로 stdout에 쓰는 내용을 구문 분석하는 것에 대해 생각했습니다 .

코드의 두 지점 사이에서 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']

업데이트 : 그들은 추가 redirect_stdout()contextlib(와 함께 파이썬 3.4 redirect_stderr()). 따라서 io.StringIO유사한 결과를 얻기 위해이를 사용할 수 있습니다 ( Capturing컨텍스트 관리자이자 목록 인 것이 틀림없이 더 편리합니다).


감사! 그리고 고급 섹션을 추가 해주셔서 감사합니다. 원래는 슬라이스 할당을 사용하여 캡처 한 텍스트를 목록에 붙인 다음 머리를 숙이고 .extend()대신 사용하여 눈치 채셨 듯이 연속적 으로 사용할 수있었습니다. :-)
kindall

추신 : 반복적으로 사용 하려면 멤버가 보유한 메모리의 일부를 해제하기 위해 메서드에 호출 self._stringio.truncate(0)뒤에 추가하는 것이 좋습니다 . self.extend()__exit__()_stringio
martineau 2013 년

25
감사합니다. Python 3의 경우 from io import StringIO컨텍스트 관리자에서 첫 번째 줄 대신 사용하십시오 .
Wtower 2015

1
스레드로부터 안전한가요? do_something이 실행되는 동안 다른 스레드 / 호출이 print ()를 사용하면 어떻게됩니까?
Derorrist

1
이 답변은 C 공유 라이브러리의 출력에는 작동하지 않습니다 . 대신 이 답변을 참조하십시오 .
craymichael

81

파이썬> = 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을 다른 파일이나 파일 류 객체로 일시적으로 리디렉션하기위한 컨텍스트 관리자.

이 도구는 출력이 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

즉, 내가 잘못했거나 stdout 로그를 잡을 수 없음을 의미합니다.
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.