Python 함수 호출에서 stdout 출력을 캡처하는 방법은 무엇입니까? (How to capture stdout output from a Python function call?)


문제 설명

Python 함수 호출에서 stdout 출력을 캡처하는 방법은 무엇입니까? (How to capture stdout output from a Python function call?)

I'm using a Python library that does something to an object

<pre class="lang‑py prettyprint‑override">do_something(my_object) </pre>

and changes it. While doing so, it prints some statistics to stdout, and I'd like to get a grip on this information. The proper solution would be to change do_something() to return the relevant information,

<pre class="lang‑py prettyprint‑override">out = do_something(my_object) </pre>

but it will be a while before the devs of do_something() get to this issue. As a workaround, I thought about parsing whatever do_something() writes to stdout.

How can I capture stdout output between two points in the code, e.g.,

<pre class="lang‑py prettyprint‑override">start_capturing() do_something(my_object) out = end_capturing() </pre>

?


참조 솔루션

방법 1:

Try this context manager:

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

Usage:

with Capturing() as output:
    do_something(my_object)

output is now a list containing the lines printed by the function call.

Advanced usage:

What may not be obvious is that this can be done more than once and the results concatenated:

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)

Output:

<pre class="lang‑none prettyprint‑override">displays on screen                      done                                    output: ['hello world', 'hello world2'] </pre>

Update: They added redirect_stdout() to contextlib in Python 3.4 (along with redirect_stderr()). So you could use io.StringIO with that to achieve a similar result (though Capturing being a list as well as a context manager is arguably more convenient).

방법 2:

In python >= 3.4, contextlib contains a redirect_stdout decorator. It can be used to answer your question like so:

import io
from contextlib import redirect_stdout

f = io.StringIO()
with redirect_stdout(f):
    do_something(my_object)
out = f.getvalue()

From the docs:

  

Context manager for temporarily redirecting sys.stdout to another file   or file‑like object.

     

This tool adds flexibility to existing functions or classes whose   output is hardwired to stdout.

     

For example, the output of help() normally is sent to sys.stdout. You   can capture that output in a string by redirecting the output to an   io.StringIO object:

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

To send the output of help() to a file on disk, redirect the output to   a regular file:

 with open('help.txt', 'w') as f:
     with redirect_stdout(f):
         help(pow)
     

To send the output of help() to sys.stderr:

with redirect_stdout(sys.stderr):
    help(pow)
     

Note that the global side effect on sys.stdout means that this context   manager is not suitable for use in library code and most threaded   applications. It also has no effect on the output of subprocesses.   However, it is still a useful approach for many utility scripts.

     

This context manager is reentrant.

방법 3:

Here is an async solution using file pipes.

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

Example usage:

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

방법 4:

Based on kindall and ForeverWintr's answer.

I create redirect_stdout function for Python<3.4:

<pre class="lang‑py prettyprint‑override">import io
from contextlib import contextmanager

@contextmanager
def redirect_stdout(f):
    try:
        _stdout = sys.stdout
        sys.stdout = f
        yield
    finally:
        sys.stdout = _stdout

f = io.StringIO()
with redirect_stdout(f):
    do_something()
out = f.getvalue()
</code></pre>

방법 5:

Also drawing on @kindall and @ForeveWintr's answers, here's a class that accomplishes this. The main difference from previous answers is that this captures it as a string, not as a StringIO object, which is much more convenient to work with!

<pre class="lang‑py prettyprint‑override">import io
from collections import UserString
from contextlib import redirect_stdout

class capture(UserString, str, redirect_stdout):
    '''
    Captures stdout (e.g., from print()) as a variable.

    Based on contextlib.redirect_stdout, but saves the user the trouble of
    defining and reading from an IO stream. Useful for testing the output of functions
    that are supposed to print certain output.
    '''

    def init(self, seq='', args, **kwargs):
        self._io = io.StringIO()
        UserString.init(self, seq=seq, 
args, **kwargs)
        redirect_stdout.init(self, self._io)
        return

    def enter(self, args, **kwargs):
        redirect_stdout.enter(self, 
args, **kwargs)
        return self

    def exit(self, args, **kwargs):
        self.data += self._io.getvalue()
        redirect_stdout.exit(self, 
args, **kwargs)
        return

    def start(self):
        self.enter()
        return self

    def stop(self):
        self.exit(None, None, None)
        return
</code></pre>

Examples:

<pre class="lang‑py prettyprint‑override"># Using with...as
with capture() as txt1:
    print('Assign these lines')
    print('to a variable')

 Using start()...stop()

txt2 = capture().start()
print('This works')
print('the same way')
txt2.stop()

print('Saved in txt1:')
print(txt1)
print('Saved in txt2:')
print(txt2)
</code></pre>

This is implemented in Sciris as sc.capture().

(by Nico SchlömerkindallForeverWintrmiXoHungerCliff Kerr)

참조 문서

  1. How to capture stdout output from a Python function call? (CC BY‑SA 3.0/4.0)

#Python #capture #stdout






관련 질문

Python - 파일 이름에 특수 문자가 있는 파일의 이름을 바꿀 수 없습니다. (Python - Unable to rename a file with special characters in the file name)

구조화된 배열의 dtype을 변경하면 문자열 데이터가 0이 됩니다. (Changing dtype of structured array zeros out string data)

목록 목록의 효과적인 구현 (Effective implementation of list of lists)

for 루프를 중단하지 않고 if 문을 중지하고 다른 if에 영향을 줍니다. (Stop if statement without breaking for loop and affect other ifs)

기본 숫자를 10 ^ 9 이상으로 늘리면 코드가 작동하지 않습니다. (Code fails to work when i increase the base numbers to anything over 10 ^ 9)

사용자 지정 대화 상자 PyQT5를 닫고 데이터 가져오기 (Close and get data from a custom dialog PyQT5)

Enthought Canopy의 Python: csv 파일 조작 (Python in Enthought Canopy: manipulating csv files)

학생의 이름을 인쇄하려고 하는 것이 잘못된 것은 무엇입니까? (What is wrong with trying to print the name of the student?)

다단계 열 테이블에 부분합 열 추가 (Adding a subtotal column to a multilevel column table)

여러 함수의 변수를 다른 함수로 사용 (Use variables from multiple functions into another function)

리프 텐서의 값을 업데이트하는 적절한 방법은 무엇입니까(예: 경사하강법 업데이트 단계 중) (What's the proper way to update a leaf tensor's values (e.g. during the update step of gradient descent))

Boto3: 조직 단위의 AMI에 시작 권한을 추가하려고 하면 ParamValidationError가 발생합니다. (Boto3: trying to add launch permission to AMI for an organizational unit raises ParamValidationError)







코멘트