Kann ich das stdout in Python in eine Art String-Puffer umleiten?

138

Ich verwende Pythons ftplib, um einen kleinen FTP-Client zu schreiben, aber einige der Funktionen im Paket geben keine Zeichenfolgenausgabe zurück, sondern drucken anstdout . Ich möchte zu stdouteinem Objekt umleiten , von dem ich die Ausgabe lesen kann.

Ich weiß, stdoutkann in jede reguläre Datei umgeleitet werden mit:

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

Ich bevorzuge jedoch eine Methode, bei der das lokale Laufwerk nicht verwendet wird.

Ich suche nach etwas wie dem BufferedReaderin Java, mit dem ein Puffer in einen Stream eingeschlossen werden kann.

Avihu Turzion
quelle
Ich denke nicht, dass stdout = open("file", "a")allein etwas umleiten wird.
Alexey

Antworten:

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()
Ned Batchelder
quelle
52
+1, Sie müssen keinen Verweis auf das Originalobjekt behalten stdout, da es immer unter verfügbar ist sys.__stdout__. Siehe docs.python.org/library/sys.html#sys.__stdout__ .
Ayman Hourieh
92
Das ist eine interessante Debatte. Das absolute Original stdout ist verfügbar, aber wenn wie diese zu ersetzen, ist es besser , ein explizites speichern zu verwenden , wie ich getan habe, da jemand anderes stdout ersetzt haben könnte , und wenn Sie verwenden stdout , würden Sie ihren Ersatz verprügeln.
Ned Batchelder
5
Würde diese Operation in einem Thread das Verhalten anderer Threads ändern? Ich meine, ist es threadsicher?
Anuvrat Parashar
6
Ich empfehle dringend, das alte Standard in einem finally:Block neu zuzuweisen , daher wird es auch neu zugewiesen, wenn dazwischen eine Ausnahme auftritt. try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn
20
Wenn Sie dies in Python 3 verwenden möchten, ersetzen Sie cStringIO durch io.
Anthony Labarre
80

In Python 3.4 gibt es die Funktion contextlib.redirect_stdout () :

import io
from contextlib import redirect_stdout

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

Das folgende Codebeispiel zeigt, wie es in älteren Python-Versionen implementiert wird .

jfs
quelle
3
Es gibt auch redirect_stderrauf dem neuesten Python!
CMCDragonkai
Ich denke, es ist nicht nötig, try / finally-Block für diese Lösung hinzuzufügen.
Snr
35

Nur um Neds Antwort oben zu ergänzen: Sie können dies verwenden, um die Ausgabe an jedes Objekt umzuleiten , das eine write (str) -Methode implementiert .

Dies kann effektiv verwendet werden, um die Standardausgabe in einer GUI-Anwendung zu "fangen".

Hier ist ein dummes Beispiel in 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
quelle
5
Funktioniert für mich mit Python 2.6 und PyQT4. Es scheint seltsam, den Arbeitscode herunterzustimmen, wenn man nicht sagen kann, warum er nicht funktioniert!
Nicolas Lefebvre
9
Vergessen Sie nicht, auch flush () hinzuzufügen!
Will
6

Ab Python 2.6 können Sie alles, was die TextIOBaseAPI aus dem io-Modul implementiert, als Ersatz verwenden. Mit dieser Lösung können Sie auch sys.stdout.buffer.write()in Python 3 (bereits) codierte Byte-Strings in stdout schreiben (siehe stdout in Python 3 ). Die Verwendung StringIOwürde dann nicht funktionieren, da weder sys.stdout.encodingnoch sys.stdout.bufferverfügbar wäre.

Eine Lösung mit 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

Diese Lösung funktioniert für Python 2> = 2.6 und Python 3.

Bitte beachten Sie, dass unser neues sys.stdout.write()nur Unicode-Zeichenfolgen und sys.stdout.buffer.write()nur Byte-Zeichenfolgen akzeptiert. Dies ist möglicherweise nicht der Fall für alten Code, aber häufig für Code, der für die Ausführung auf Python 2 und 3 ohne Änderungen erstellt wurde, was wiederum häufig verwendet wird sys.stdout.buffer.

Sie können eine geringfügige Variation erstellen, die Unicode- und Byte-Zeichenfolgen akzeptiert für 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)

Sie müssen die Codierung des Puffers nicht auf sys.stdout.encoding festlegen, dies ist jedoch hilfreich, wenn Sie diese Methode zum Testen / Vergleichen der Skriptausgabe verwenden.

JonnyJD
quelle
Diese Antwort half mir beim Einrichten des stdout-Parameters eines Umgebungsobjekts für die Verwendung mit Httpies core.py.
Fragorl
6

Diese Methode stellt sys.stdout auch dann wieder her, wenn eine Ausnahme vorliegt. Es wird auch eine Ausgabe vor der Ausnahme erhalten.

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

Getestet in Python 2.7.10 mit io.BytesIO()

Getestet in Python 3.6.4 mit io.StringIO()


Bob, hinzugefügt für einen Fall, wenn Sie der Meinung sind, dass etwas aus dem Experiment mit modifiziertem / erweitertem Code in irgendeiner Weise interessant werden könnte. Andernfalls können Sie es jederzeit löschen

Ad informandum ... ein paar Bemerkungen aus ausgedehnten Experimenten, während einige brauchbare Mechaniken gefunden wurden, um Ausgaben zu "greifen", die numexpr.print_versions()direkt an die <stdout>(bei der Notwendigkeit, die GUI zu bereinigen und Details in einem Debugging-Bericht zu sammeln) gerichtet wurden.

# 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
quelle
6

Ein Kontextmanager für 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()

Verwendung wie folgt:

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

In Python3.6 sind die Module StringIOund cStringIOweg. Sie sollten sie io.StringIOstattdessen verwenden. Sie sollten dies also wie die erste Antwort tun:

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
quelle
1
Sie können die Qualität Ihrer Antwort verbessern, indem Sie erklären, wie der obige Code funktioniert und wie dies eine Verbesserung gegenüber der Situation des Fragestellers darstellt.
Toonice
1

Hier ist eine andere Sichtweise. contextlib.redirect_stdoutmit io.StringIO()wie dokumentiert ist toll, aber es ist immer noch ein bisschen ausführlich für den täglichen Gebrauch. So machen Sie es durch Unterklassen zu einem Einzeiler 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()  

Da __enter__ self zurückgibt, steht Ihnen das Kontextmanagerobjekt nach dem Beenden des Blocks zur Verfügung. Darüber hinaus ist dank der __repr__ -Methode die Zeichenfolgendarstellung des Kontextmanagerobjekts tatsächlich stdout. Also jetzt hast du,

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