Làm thế nào để nắm bắt đầu ra stdout từ một lệnh gọi hàm Python?


112

Tôi đang sử dụng một thư viện Python thực hiện điều gì đó với một đối tượng

do_something(my_object)

và thay đổi nó. Trong khi làm như vậy, nó sẽ in một số thống kê ra stdout và tôi muốn nắm được thông tin này. Giải pháp thích hợp sẽ là thay đổi do_something()để trả lại thông tin liên quan,

out = do_something(my_object)

nhưng sẽ phải mất một thời gian trước khi các nhà phát triển do_something()giải quyết vấn đề này. Để giải quyết vấn đề, tôi đã nghĩ đến việc phân tích cú pháp bất cứ thứ gì do_something()viết vào stdout.

Làm cách nào tôi có thể nắm bắt đầu ra stdout giữa hai điểm trong mã, ví dụ:

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

?


2
Có thể trùng lặp kết quả chụp đĩa
Martijn Pieters

Câu trả lời của tôi trong câu hỏi được liên kết cũng áp dụng ở đây.
Martijn Pieters

Tôi đã cố gắng làm điều đó một lần và câu trả lời tốt nhất mà tôi tìm thấy là: stackoverflow.com/a/3113913/1330293
elyase

Câu trả lời được liên kết @elyase là một giải pháp thanh lịch
Pykler

Hãy xem câu trả lời này .
martineau

Câu trả lời:


183

Hãy thử trình quản lý ngữ cảnh này:

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

Sử dụng:

with Capturing() as output:
    do_something(my_object)

output bây giờ là một danh sách chứa các dòng được in bởi lệnh gọi hàm.

Cách sử dụng nâng cao:

Điều có thể không rõ ràng là điều này có thể được thực hiện nhiều lần và kết quả được nối với nhau:

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)

Đầu ra:

displays on screen                     
done                                   
output: ['hello world', 'hello world2']

Cập nhật : Họ nói thêm redirect_stdout()để contextlibbằng Python 3.4 (cùng với redirect_stderr()). Vì vậy, bạn có thể sử dụng io.StringIOnó để đạt được kết quả tương tự (mặc dù Capturingviệc trở thành danh sách cũng như trình quản lý ngữ cảnh được cho là tiện lợi hơn).


Cảm ơn! Và cảm ơn bạn đã thêm phần nâng cao ... Ban đầu tôi sử dụng một phép gán lát để gắn văn bản đã chụp vào danh sách, sau đó tôi tự nghĩ ra và sử dụng .extend()thay thế để nó có thể được sử dụng đồng thời, như bạn đã nhận thấy. :-)
kindall

Tái bút: Nếu nó sẽ được sử dụng nhiều lần, tôi khuyên bạn nên thêm dấu self._stringio.truncate(0)sau self.extend()lời gọi trong __exit__()phương thức để giải phóng một số bộ nhớ do _stringiothành viên nắm giữ .
martineau

25
Câu trả lời tuyệt vời, cảm ơn. Đối với Python 3, sử dụng from io import StringIOthay vì dòng đầu tiên trong trình quản lý ngữ cảnh.
Wtower

1
Chủ đề này có an toàn không? Điều gì xảy ra nếu một số luồng / cuộc gọi khác sử dụng print () trong khi do_something chạy?
Kẻ khủng bố

1
Câu trả lời này sẽ không hoạt động đối với đầu ra từ thư viện được chia sẻ C, hãy xem câu trả lời này để thay thế.
craymichael

81

Trong python> = 3.4, contextlib chứa trình redirect_stdouttrang trí. Nó có thể được sử dụng để trả lời câu hỏi của bạn như sau:

import io
from contextlib import redirect_stdout

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

Từ các tài liệu :

Trình quản lý ngữ cảnh để tạm thời chuyển hướng sys.stdout đến một tệp hoặc đối tượng giống tệp khác.

Công cụ này bổ sung tính linh hoạt cho các hàm hoặc lớp hiện có mà đầu ra của nó được kết nối cứng với stdout.

Ví dụ: đầu ra của help () thường được gửi đến sys.stdout. Bạn có thể nắm bắt đầu ra đó trong một chuỗi bằng cách chuyển hướng đầu ra đến một đối tượng io.StringIO:

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

Để gửi đầu ra của help () đến một tệp trên đĩa, hãy chuyển hướng đầu ra đến một tệp thông thường:

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

Để gửi đầu ra của help () tới sys.stderr:

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

Lưu ý rằng tác dụng phụ toàn cục trên sys.stdout có nghĩa là trình quản lý ngữ cảnh này không thích hợp để sử dụng trong mã thư viện và hầu hết các ứng dụng phân luồng. Nó cũng không ảnh hưởng đến đầu ra của các quy trình con. Tuy nhiên, nó vẫn là một cách tiếp cận hữu ích cho nhiều tập lệnh tiện ích.

Trình quản lý ngữ cảnh này được sử dụng lại.


khi đã thử 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'). Nó mang lại cho tôi một lỗi:AssertionError: '' != 'Test debug message'
Eziz Durdyyev

có nghĩa là tôi đã làm sai điều gì đó hoặc nó không thể bắt nhật ký stdout.
Eziz Durdyyev

@EzizDurdyyev, logger.debugkhông ghi vào stdout theo mặc định. Nếu bạn thay thế nhật ký cuộc gọi của mình bằng, print()bạn sẽ thấy thông báo.
ForeverWintr

Vâng, tôi biết, nhưng tôi làm cho nó viết thư cho stdout như vậy: stream_handler = logging.StreamHandler(sys.stdout). Và thêm trình xử lý đó vào trình ghi nhật ký của tôi. vì vậy nó nên viết thành stdout và redirect_stdoutnên bắt nó, phải không?
Eziz Durdyyev

Tôi nghi ngờ vấn đề là do cách bạn đã định cấu hình trình ghi nhật ký của mình. Tôi sẽ xác minh rằng nó in ra stdout mà không có redirect_stdout. Nếu có, có thể bộ đệm không được xóa cho đến khi trình quản lý ngữ cảnh thoát.
ForeverWintr

0

Đây là một giải pháp không đồng bộ bằng cách sử dụng đường ống tệp.

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

Ví dụ sử dụng:

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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.