Dapatkah saya mengarahkan stdout dengan python ke dalam semacam buffer string?

141

Saya menggunakan python ftplib untuk menulis klien FTP kecil, tetapi beberapa fungsi dalam paket tidak mengembalikan output string, tetapi mencetak ke stdout. Saya ingin mengarahkan stdoutke objek yang hasilnya akan dapat saya baca.

aku tahu stdout bisa dialihkan ke file biasa dengan:

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

Tapi saya lebih suka metode yang tidak menggunakan drive lokal.

Saya mencari sesuatu seperti BufferedReaderdi Java yang dapat digunakan untuk membungkus buffer ke dalam aliran.

Avihu Turzion
sumber
Saya tidak berpikir stdout = open("file", "a")dengan sendirinya akan mengarahkan apapun.
Alexey

Jawaban:

216
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()
Ned Batchelder
sumber
54
+1, Anda tidak perlu menyimpan referensi ke stdoutobjek asli , karena selalu tersedia di sys.__stdout__. Lihat docs.python.org/library/sys.html#sys.__stdout__ .
Ayman Hourieh
94
Nah, itu debat yang menarik. Stdout asli absolut tersedia, tetapi ketika mengganti seperti ini, lebih baik menggunakan penyimpanan eksplisit seperti yang telah saya lakukan, karena orang lain dapat menggantikan stdout dan jika Anda menggunakan stdout , Anda akan mengalahkan penggantinya.
Ned Batchelder
5
akankah operasi di satu utas ini mengubah perilaku utas lainnya? Maksud saya, apakah ini aman untuk benang?
Anuvrat Parashar
8
Saya sangat menyarankan untuk menetapkan kembali stdout lama dalam satu finally:blok, jadi itu juga ditugaskan kembali jika pengecualian meningkat di antaranya. try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn
21
Jika Anda ingin menggunakan ini dengan Python 3, ganti cStringIO dengan io.
Anthony Labarre
35

Hanya untuk menambah jawaban Ned di atas: Anda dapat menggunakan ini untuk mengarahkan output ke objek apa pun yang mengimplementasikan metode write (str) .

Ini dapat digunakan untuk efek yang baik untuk "menangkap" keluaran stdout dalam aplikasi GUI.

Berikut contoh konyol di 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 !"
Nicolas Lefebvre
sumber
5
Bekerja untuk saya dengan python 2.6 dan PyQT4. Tampaknya aneh untuk menurunkan kode kerja suara ketika Anda tidak tahu mengapa itu tidak berhasil!
Nicolas Lefebvre
9
jangan lupa tambahkan flush () juga!
Akankah
7

Metode ini memulihkan sys.stdout meskipun ada pengecualian. Itu juga mendapat keluaran apa pun sebelum pengecualian.

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

Diuji dengan Python 2.7.10 menggunakan io.BytesIO()

Diuji dengan Python 3.6.4 menggunakan io.StringIO()


Bob, ditambahkan untuk kasus jika Anda merasa sesuatu dari eksperimen kode yang dimodifikasi / diperpanjang mungkin menarik dalam arti apa pun, jika tidak silakan hapus

Ad informandum ... beberapa komentar dari eksperimen yang diperpanjang selama menemukan beberapa mekanisme yang layak untuk "mengambil" keluaran, diarahkan numexpr.print_versions()langsung ke <stdout>(atas kebutuhan untuk membersihkan GUI dan mengumpulkan detail ke dalam laporan debugging)

# 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
'''
Bob Stein
sumber
6

Dimulai dengan Python 2.6 Anda dapat menggunakan apa pun yang mengimplementasikan TextIOBaseAPI dari modul io sebagai penggantinya. Solusi ini juga memungkinkan Anda menggunakan sys.stdout.buffer.write()Python 3 untuk menulis (sudah) string byte yang dikodekan ke stdout (lihat stdout di Python 3 ). Menggunakan StringIOtidak akan berhasil saat itu, karena tidak sys.stdout.encodingjugasys.stdout.buffer akan tersedia.

Solusi menggunakan 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

Solusi ini berfungsi untuk Python 2> = 2.6 dan Python 3.

Harap dicatat bahwa baru kami sys.stdout.write()hanya menerima string unicode dan sys.stdout.buffer.write()hanya menerima string byte. Ini mungkin bukan kasus untuk kode lama, tetapi sering kali kasus kode yang dibuat untuk berjalan di Python 2 dan 3 tanpa perubahan, yang lagi-lagi sering menggunakansys.stdout.buffer .

Anda dapat membuat sedikit variasi yang menerima string unicode dan byte untuk 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)

Anda tidak perlu menyetel pengkodean buffer sys.stdout.encoding, tetapi ini membantu saat menggunakan metode ini untuk menguji / membandingkan keluaran skrip.

JonnyJD
sumber
Jawaban ini membantu saya saat menyiapkan parameter stdout objek Lingkungan untuk digunakan dengan Httpie core.py.
fragorl
6

Manajer konteks untuk 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()

gunakan seperti ini:

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

Di Python3.6, modul StringIOdan cStringIOsudah tidak ada, Anda harus menggunakan sebagai io.StringIOgantinya, jadi Anda harus melakukan ini seperti jawaban pertama:

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()
haofly
sumber
1
Anda dapat meningkatkan kualitas Jawaban Anda dengan menjelaskan bagaimana kode di atas bekerja dan bagaimana ini merupakan perbaikan atas situasi Penanya.
toonice
1

Ini pendapat lain tentang ini. contextlib.redirect_stdoutdengan io.StringIO()seperti yang didokumentasikan itu bagus, tapi masih sedikit bertele-tele untuk penggunaan sehari-hari. Berikut cara membuatnya menjadi satu baris dengan subclassing 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()  

Sejak __enter__ mengembalikan diri, Anda memiliki objek pengelola konteks yang tersedia setelah blok dengan keluar. Selain itu, berkat metode __repr__, representasi string dari objek pengelola konteks sebenarnya adalah stdout. Jadi sekarang kamu punya,

with capture() as message:
    print('Hello World!')
print(str(message)=='Hello World!\n')  # returns True
pandichef
sumber