ฉันสามารถเปลี่ยนเส้นทาง stdout ใน python เป็นสตริงบัฟเฟอร์บางประเภทได้หรือไม่


141

ฉันใช้ python ftplibเพื่อเขียนไคลเอนต์ FTP ขนาดเล็ก แต่ฟังก์ชั่นบางอย่างในแพ็คเกจไม่ส่งคืนเอาต์พุตสตริง แต่พิมพ์เป็นstdoutไฟล์. ฉันต้องการเปลี่ยนเส้นทางstdoutไปยังวัตถุที่ฉันจะสามารถอ่านผลลัพธ์ได้

ฉันรู้ว่าstdoutสามารถเปลี่ยนเส้นทางไปยังไฟล์ปกติได้ด้วย:

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

แต่ฉันชอบวิธีที่ไม่ใช้โลคัลไดรฟ์มากกว่า

ฉันกำลังมองหาบางอย่างเช่นBufferedReaderใน Java ที่สามารถใช้เพื่อห่อบัฟเฟอร์ลงในสตรีม


ฉันไม่คิดว่าstdout = open("file", "a")ตัวเองจะเปลี่ยนเส้นทางอะไร
Alexey

คำตอบ:


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

54
+1 คุณไม่จำเป็นต้องให้มีการอ้างอิงไปที่เดิมวัตถุมันเป็นเสมอที่สามารถใช้ได้ที่stdout sys.__stdout__ดูdocs.python.org/library/sys.html#sys.__stdout__
Ayman Hourieh

94
นั่นเป็นข้อถกเถียงที่น่าสนใจ stdout ดั้งเดิมแน่นอนมีให้ใช้งาน แต่เมื่อเปลี่ยนแบบนี้ควรใช้การบันทึกอย่างชัดเจนเหมือนที่ฉันเคยทำมาเนื่องจากมีคนอื่นมาแทนที่ stdout และถ้าคุณใช้stdoutคุณจะต้องเปลี่ยนแทน
Ned Batchelder

5
การดำเนินการนี้ในเธรดเดียวจะเปลี่ยนแปลงพฤติกรรมของเธรดอื่นหรือไม่ ฉันหมายความว่ามันปลอดภัยไหม
Anuvrat Parashar

8
ฉันขอแนะนำอย่างยิ่งให้กำหนด stdout เก่าใหม่ในfinally:บล็อกดังนั้นจึงมีการกำหนดใหม่ด้วยหากมีข้อยกเว้นเพิ่มขึ้นระหว่าง try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn

21
หากคุณต้องการใช้สิ่งนี้ใน Python 3 ให้แทนที่ cStringIO ด้วย io
Anthony Labarre

84

มีฟังก์ชัน contextlib.redirect_stdout ()ใน Python 3.4:

import io
from contextlib import redirect_stdout

with io.StringIO() as buf, redirect_stdout(buf):
    print('redirected')
    output = buf.getvalue()

นี่คือตัวอย่างโค้ดที่แสดงให้เห็นถึงวิธีการที่จะใช้มันในรุ่นเก่าหลาม


4
นอกจากนี้ยังredirect_stderrมี Python ล่าสุดด้วย!
CMCDragonkai

ฉันคิดว่าไม่จำเป็นต้องเพิ่ม try / block สำหรับโซลูชันนี้
snr

35

เพียงเพื่อเพิ่มคำตอบของเน็ดข้างต้น: คุณสามารถใช้เพื่อการส่งออกการเปลี่ยนเส้นทางไปยังวัตถุใด ๆ ที่ดำเนินการเขียน (STR) วิธีการ

สิ่งนี้สามารถใช้เพื่อสร้างเอฟเฟกต์ที่ดีในการ "จับ" เอาต์พุต stdout ในแอปพลิเคชัน GUI

นี่เป็นตัวอย่างโง่ ๆ ใน 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
ใช้ได้กับฉันกับ python 2.6 และ PyQT4 ดูเหมือนจะแปลกที่จะลงรหัสการทำงานเมื่อคุณไม่สามารถบอกได้ว่าทำไมถึงไม่ทำงาน
Nicolas Lefebvre

9
อย่าลืมเติมฟลัช () ด้วย!
จะ

7

วิธีนี้จะคืนค่า sys.stdout แม้ว่าจะมีข้อยกเว้นก็ตาม นอกจากนี้ยังได้รับเอาต์พุตใด ๆ ก่อนข้อยกเว้น

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

ทดสอบใน Python 2.7.10 โดยใช้ io.BytesIO()

ทดสอบใน Python 3.6.4 โดยใช้ io.StringIO()


Bob เพิ่มในกรณีที่คุณรู้สึกว่าอะไรจากการทดสอบโค้ดที่แก้ไข / ขยายอาจน่าสนใจในแง่ใดก็ตามมิฉะนั้นอย่าลังเลที่จะลบมัน

Ad informandum ... ข้อสังเกตบางประการจากการทดลองเพิ่มเติมในระหว่างการค้นหากลไกที่ใช้งานได้เพื่อ "คว้า" ผลลัพธ์ที่ส่งnumexpr.print_versions()ตรงไปยังส่วน<stdout>(เมื่อจำเป็นต้องทำความสะอาด GUI และรวบรวมรายละเอียดในรายงานการแก้ไขจุดบกพร่อง)

# 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

เริ่มต้นด้วย Python 2.6 คุณสามารถใช้อะไรก็ได้ที่ใช้TextIOBaseAPIจากโมดูล io แทน โซลูชันนี้ยังช่วยให้คุณสามารถใช้sys.stdout.buffer.write()ใน Python 3 เพื่อเขียนสตริงไบต์ที่เข้ารหัส (แล้ว) เป็น stdout (ดูstdout ใน Python 3 ) การใช้StringIOจะไม่ทำงานแล้วเพราะค่าsys.stdout.encodingมิได้sys.stdout.bufferจะใช้ได้

วิธีแก้ปัญหาโดยใช้ 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

วิธีนี้ใช้ได้กับ Python 2> = 2.6 และ Python 3

โปรดทราบว่าใหม่ของเราsys.stdout.write()ยอมรับเฉพาะสตริง Unicode และsys.stdout.buffer.write()ยอมรับเฉพาะสตริงไบต์เท่านั้น นี่อาจไม่ใช่กรณีของรหัสเก่า แต่มักเป็นกรณีของรหัสที่สร้างขึ้นเพื่อทำงานบน Python 2 และ 3 โดยไม่มีการเปลี่ยนแปลงซึ่งมักจะใช้ประโยชน์จากสิ่งsys.stdout.bufferนี้อีกครั้ง

คุณสามารถสร้างรูปแบบเล็กน้อยที่ยอมรับสตริงยูนิโค้ดและไบต์สำหรับ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)

คุณไม่จำเป็นต้องตั้งค่าการเข้ารหัสของบัฟเฟอร์ sys.stdout.encoding แต่จะช่วยได้เมื่อใช้วิธีนี้ในการทดสอบ / เปรียบเทียบเอาต์พุตสคริปต์


คำตอบนี้ช่วยฉันเมื่อตั้งค่าพารามิเตอร์ stdout ของวัตถุ Environment เพื่อใช้กับ core.py ของ Httpie
Fragorl

6

ตัวจัดการบริบทสำหรับ 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()

ใช้แบบนี้:

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

4

ใน Python3.6 โมดูลStringIOและcStringIOโมดูลหายไปคุณควรใช้io.StringIOแทนดังนั้นคุณควรทำสิ่งนี้เหมือนคำตอบแรก:

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
คุณสามารถปรับปรุงคุณภาพของคำตอบของคุณได้โดยอธิบายว่าโค้ดด้านบนทำงานอย่างไรและสิ่งนี้เป็นการปรับปรุงสถานการณ์ของผู้ถามอย่างไร
toonice


1

นี่คืออีกสิ่งหนึ่งในเรื่องนี้ contextlib.redirect_stdoutด้วยio.StringIO()ตามที่บันทึกไว้นั้นยอดเยี่ยม แต่ก็ยังใช้งานได้ทุกวัน ต่อไปนี้เป็นวิธีทำให้เป็นซับเดียวโดยคลาสย่อย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()  

เนื่องจาก __enter__ ส่งคืนตัวเองคุณจึงมีอ็อบเจ็กต์ตัวจัดการบริบทพร้อมใช้งานหลังจากปิดกั้นด้วย ยิ่งไปกว่านั้นด้วยเมธอด __repr__ การแสดงสตริงของอ็อบเจ็กต์ตัวจัดการบริบทคือ stdout ตอนนี้คุณมี

with capture() as message:
    print('Hello World!')
print(str(message)=='Hello World!\n')  # returns True
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.