Tôi có thể chuyển hướng thiết bị xuất chuẩn trong python vào một số loại bộ đệm chuỗi không?


138

Tôi đang sử dụng python ftplibđể viết một máy khách FTP nhỏ, nhưng một số hàm trong gói không trả về đầu ra chuỗi, nhưng in ra stdout. Tôi muốn chuyển hướng stdoutđến một đối tượng mà tôi có thể đọc đầu ra từ đó.

Tôi biết stdoutcó thể được chuyển hướng vào bất kỳ tập tin thông thường nào với:

stdout = open("file", "a")

Nhưng tôi thích một phương pháp không sử dụng ổ đĩa cục bộ.

Tôi đang tìm kiếm một cái gì đó giống như BufferedReadertrong Java có thể được sử dụng để bọc một bộ đệm thành một luồng.


Tôi không nghĩ rằng stdout = open("file", "a")chính nó sẽ chuyển hướng bất cứ điều gì.
Alexey

Câu trả lời:


209
from cStringIO import StringIO # Python3 use: from io import StringIO
import sys

old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()

# blah blah lots of code ...

sys.stdout = old_stdout

# examine mystdout.getvalue()

52
+1, bạn không cần phải giữ tham chiếu đến stdoutđối tượng ban đầu , vì nó luôn có sẵn tại sys.__stdout__. Xem docs.python.org/l Library / sys.html # sys.__stdout__ .
Ayman Hourieh

92
Vâng, đó là một cuộc tranh luận thú vị. Thiết bị xuất chuẩn ban đầu tuyệt đối có sẵn, nhưng khi thay thế như thế này, tốt hơn là sử dụng một lưu rõ ràng như tôi đã làm, vì người khác có thể đã thay thế thiết bị xuất chuẩn và nếu bạn sử dụng thiết bị xuất chuẩn , bạn sẽ ghi đè thay thế.
Ned Batchelder

5
hoạt động này trong một chủ đề sẽ thay đổi hành vi của các chủ đề khác? Ý tôi là nó có an toàn không?
Anuvrat Parashar

6
Tôi đặc biệt khuyên bạn nên gán lại thiết bị xuất chuẩn cũ trong một finally:khối, vì vậy nó cũng được chỉ định lại nếu có ngoại lệ xuất hiện ở giữa. try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn

20
Nếu bạn muốn sử dụng cái này trong Python 3, hãy thay thế cStringIO bằng io.
Anthony Labarre


35

Chỉ cần thêm vào câu trả lời của Ned ở trên: bạn có thể sử dụng điều này để chuyển hướng đầu ra đến bất kỳ đối tượng nào thực hiện phương thức write (str) .

Điều này có thể được sử dụng để có hiệu quả tốt để "bắt" đầu ra thiết bị xuất chuẩn trong ứng dụng GUI.

Đây là một ví dụ ngớ ngẩn trong PyQt:

import sys
from PyQt4 import QtGui

class OutputWindow(QtGui.QPlainTextEdit):
    def write(self, txt):
        self.appendPlainText(str(txt))

app = QtGui.QApplication(sys.argv)
out = OutputWindow()
sys.stdout=out
out.show()
print "hello world !"

5
Hoạt động với tôi với python 2.6 và PyQT4. Có vẻ lạ khi bỏ phiếu làm việc khi bạn không thể biết tại sao nó không hoạt động!
Nicolas Lefebvre

9
đừng quên thêm flush () quá!
Sẽ

6

Bắt đầu với Python 2.6, bạn có thể sử dụng mọi thứ triển khai TextIOBaseAPI từ mô-đun io để thay thế. Giải pháp này cũng cho phép bạn sử dụng sys.stdout.buffer.write()trong Python 3 để ghi các chuỗi byte được mã hóa (đã) vào thiết bị xuất chuẩn (xem thiết bị xuất chuẩn trong Python 3 ). Sử dụng StringIOsẽ không hoạt động sau đó, bởi vì không phải sys.stdout.encodingcũng không sys.stdout.buffercó sẵn.

Một giải pháp sử dụng TextIOWrapper:

import sys
from io import TextIOWrapper, BytesIO

# setup the environment
old_stdout = sys.stdout
sys.stdout = TextIOWrapper(BytesIO(), sys.stdout.encoding)

# do something that writes to stdout or stdout.buffer

# 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

Giải pháp này hoạt động cho Python 2> = 2.6 và Python 3.

Xin lưu ý rằng mới của chúng tôi sys.stdout.write()chỉ chấp nhận chuỗi unicode và sys.stdout.buffer.write()chỉ chấp nhận chuỗi byte. Đây có thể không phải là trường hợp của mã cũ, nhưng thường là trường hợp mã được xây dựng để chạy trên Python 2 và 3 mà không thay đổi, điều này thường sử dụng lại sys.stdout.buffer.

Bạn có thể xây dựng một biến thể nhỏ chấp nhận chuỗi unicode và byte cho write():

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)

Bạn không phải thiết lập mã hóa bộ đệm sys.stdout.encoding, nhưng điều này sẽ giúp khi sử dụng phương pháp này để kiểm tra / so sánh đầu ra tập lệnh.


Câu trả lời này đã giúp tôi khi thiết lập thông số xuất sắc của đối tượng Môi trường để sử dụng với core.txt của http.
Fragorl

6

Phương pháp này khôi phục sys.stdout ngay cả khi có ngoại lệ. Nó cũng nhận được bất kỳ đầu ra trước ngoại lệ.

import io
import sys

real_stdout = sys.stdout
fake_stdout = io.BytesIO()   # or perhaps io.StringIO()
try:
    sys.stdout = fake_stdout
    # do what you have to do to create some output
finally:
    sys.stdout = real_stdout
    output_string = fake_stdout.getvalue()
    fake_stdout.close()
    # do what you want with the output_string

Đã thử nghiệm trong Python 2.7.10 bằng cách sử dụng io.BytesIO()

Đã thử nghiệm trong Python 3.6.4 bằng cách sử dụng io.StringIO()


Bob, được thêm vào cho một trường hợp nếu bạn cảm thấy bất cứ điều gì từ thử nghiệm mã được sửa đổi / mở rộng có thể trở nên thú vị theo bất kỳ ý nghĩa nào, nếu không, hãy thoải mái xóa nó

Thông tin quảng cáo ... một vài nhận xét từ thử nghiệm mở rộng trong khi tìm một số cơ chế khả thi để "lấy" các đầu ra, được dẫn numexpr.print_versions()trực tiếp đến <stdout>(khi cần làm sạch GUI và thu thập chi tiết vào báo cáo gỡ lỗi)

# THIS WORKS AS HELL: as Bob Stein proposed years ago:
#  py2 SURPRISEDaBIT:
#
import io
import sys
#
real_stdout = sys.stdout                        #           PUSH <stdout> ( store to REAL_ )
fake_stdout = io.BytesIO()                      #           .DEF FAKE_
try:                                            # FUSED .TRY:
    sys.stdout.flush()                          #           .flush() before
    sys.stdout = fake_stdout                    #           .SET <stdout> to use FAKE_
    # ----------------------------------------- #           +    do what you gotta do to create some output
    print 123456789                             #           + 
    import  numexpr                             #           + 
    QuantFX.numexpr.__version__                 #           + [3] via fake_stdout re-assignment, as was bufferred + "late" deferred .get_value()-read into print, to finally reach -> real_stdout
    QuantFX.numexpr.print_versions()            #           + [4] via fake_stdout re-assignment, as was bufferred + "late" deferred .get_value()-read into print, to finally reach -> real_stdout
    _ = os.system( 'echo os.system() redir-ed' )#           + [1] via real_stdout                                 + "late" deferred .get_value()-read into print, to finally reach -> real_stdout, if not ( _ = )-caught from RET-d "byteswritten" / avoided from being injected int fake_stdout
    _ = os.write(  sys.stderr.fileno(),         #           + [2] via      stderr                                 + "late" deferred .get_value()-read into print, to finally reach -> real_stdout, if not ( _ = )-caught from RET-d "byteswritten" / avoided from being injected int fake_stdout
                       b'os.write()  redir-ed' )#  *OTHERWISE, if via fake_stdout, EXC <_io.BytesIO object at 0x02C0BB10> Traceback (most recent call last):
    # ----------------------------------------- #           ?                              io.UnsupportedOperation: fileno
    #'''                                                    ? YET:        <_io.BytesIO object at 0x02C0BB10> has a .fileno() method listed
    #>>> 'fileno' in dir( sys.stdout )       -> True        ? HAS IT ADVERTISED,
    #>>> pass;            sys.stdout.fileno  -> <built-in method fileno of _io.BytesIO object at 0x02C0BB10>
    #>>> pass;            sys.stdout.fileno()-> Traceback (most recent call last):
    #                                             File "<stdin>", line 1, in <module>
    #                                           io.UnsupportedOperation: fileno
    #                                                       ? BUT REFUSES TO USE IT
    #'''
finally:                                        # == FINALLY:
    sys.stdout.flush()                          #           .flush() before ret'd back REAL_
    sys.stdout = real_stdout                    #           .SET <stdout> to use POP'd REAL_
    sys.stdout.flush()                          #           .flush() after  ret'd back REAL_
    out_string = fake_stdout.getvalue()         #           .GET string           from FAKE_
    fake_stdout.close()                         #                <FD>.close()
    # +++++++++++++++++++++++++++++++++++++     # do what you want with the out_string
    #
    print "\n{0:}\n{1:}{0:}".format( 60 * "/\\",# "LATE" deferred print the out_string at the very end reached -> real_stdout
                                     out_string #                   
                                     )
'''
PASS'd:::::
...
os.system() redir-ed
os.write()  redir-ed
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
123456789
'2.5'
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Numexpr version:   2.5
NumPy version:     1.10.4
Python version:    2.7.13 |Anaconda 4.0.0 (32-bit)| (default, May 11 2017, 14:07:41) [MSC v.1500 32 bit (Intel)]
AMD/Intel CPU?     True
VML available?     True
VML/MKL version:   Intel(R) Math Kernel Library Version 11.3.1 Product Build 20151021 for 32-bit applications
Number of threads used by default: 4 (out of 4 detected cores)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
>>>

EXC'd :::::
...
os.system() redir-ed
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
123456789
'2.5'
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Numexpr version:   2.5
NumPy version:     1.10.4
Python version:    2.7.13 |Anaconda 4.0.0 (32-bit)| (default, May 11 2017, 14:07:41) [MSC v.1500 32 bit (Intel)]
AMD/Intel CPU?     True
VML available?     True
VML/MKL version:   Intel(R) Math Kernel Library Version 11.3.1 Product Build 20151021 for 32-bit applications
Number of threads used by default: 4 (out of 4 detected cores)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

Traceback (most recent call last):
  File "<stdin>", line 9, in <module>
io.UnsupportedOperation: fileno
'''

6

Trình quản lý bối cảnh cho python3:

import sys
from io import StringIO


class RedirectedStdout:
    def __init__(self):
        self._stdout = None
        self._string_io = None

    def __enter__(self):
        self._stdout = sys.stdout
        sys.stdout = self._string_io = StringIO()
        return self

    def __exit__(self, type, value, traceback):
        sys.stdout = self._stdout

    def __str__(self):
        return self._string_io.getvalue()

sử dụng như thế này:

>>> with RedirectedStdout() as out:
>>>     print('asdf')
>>>     s = str(out)
>>>     print('bsdf')
>>> print(s, out)
'asdf\n' 'asdf\nbsdf\n'

4

Trong Python3.6, các mô-đun StringIOcStringIObiến mất, bạn nên sử dụng io.StringIOthay vào đó. Vì vậy, bạn nên làm điều này giống như câu trả lời đầu tiên:

import sys
from io import StringIO

old_stdout = sys.stdout
old_stderr = sys.stderr
my_stdout = sys.stdout = StringIO()
my_stderr = sys.stderr = StringIO()

# blah blah lots of code ...

sys.stdout = self.old_stdout
sys.stderr = self.old_stderr

// if you want to see the value of redirect output, be sure the std output is turn back
print(my_stdout.getvalue())
print(my_stderr.getvalue())

my_stdout.close()
my_stderr.close()

1
Bạn có thể cải thiện chất lượng Câu trả lời của mình bằng cách giải thích cách mã trên hoạt động và cách đây là một cải tiến đối với tình huống của Người hỏi.
toonice


1

Đây là một mất đi về điều này. contextlib.redirect_stdoutvới io.StringIO()như ghi nhận là rất tốt, nhưng nó vẫn còn một chút verbose cho sử dụng hàng ngày. Đây là cách làm cho nó trở thành một lớp lót bằng cách phân lớp contextlib.redirect_stdout:

import sys
import io
from contextlib import redirect_stdout

class capture(redirect_stdout):

    def __init__(self):
        self.f = io.StringIO()
        self._new_target = self.f
        self._old_targets = []  # verbatim from parent class

    def __enter__(self):
        self._old_targets.append(getattr(sys, self._stream))  # verbatim from parent class
        setattr(sys, self._stream, self._new_target)  # verbatim from parent class
        return self  # instead of self._new_target in the parent class

    def __repr__(self):
        return self.f.getvalue()  

Vì __enter__ tự trả về, bạn có sẵn đối tượng trình quản lý bối cảnh sau khi thoát với khối. Hơn nữa, nhờ vào phương thức __Vpr__, biểu diễn chuỗi của đối tượng trình quản lý bối cảnh, trên thực tế, là thiết bị xuất chuẩn. Vì vậy, bây giờ bạn có,

with capture() as message:
    print('Hello World!')
print(str(message)=='Hello World!\n')  # returns True
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.