Posso reindirizzare lo stdout in Python in una sorta di buffer di stringhe?


138

Sto usando Python ftplibper scrivere un piccolo client FTP, ma alcune delle funzioni nel pacchetto non restituiscono l'output della stringa, ma stampano su stdout. Voglio reindirizzare stdouta un oggetto da cui sarò in grado di leggere l'output.

So che stdoutpuò essere reindirizzato in qualsiasi file normale con:

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

Ma preferisco un metodo che non utilizza l'unità locale.

Sto cercando qualcosa di simile BufferedReadera Java che può essere utilizzato per avvolgere un buffer in un flusso.


Non penso che stdout = open("file", "a")da solo reindirizzerà nulla.
Alexey,

Risposte:


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, non è necessario mantenere un riferimento stdoutall'oggetto originale , poiché è sempre disponibile all'indirizzo sys.__stdout__. Vedi docs.python.org/library/sys.html#sys.__stdout__ .
Ayman Hourieh,

92
Bene, questo è un dibattito interessante. Lo stdout originale assoluto è disponibile, ma quando si sostituisce in questo modo, è meglio utilizzare un salvataggio esplicito come ho fatto, poiché qualcun altro avrebbe potuto sostituire stdout e se si utilizza stdout , si intaserebbe la loro sostituzione.
Ned Batchelder,

5
questa operazione in un thread altererebbe il comportamento di altri thread? Voglio dire, è sicuro per i thread?
Anuvrat Parashar,

6
Consiglio vivamente di riassegnare il vecchio stdout in un finally:blocco, quindi viene anche riassegnato se viene sollevata un'eccezione nel mezzo. try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn,

20
Se vuoi usarlo in Python 3, sostituisci cStringIO con io.
Anthony Labarre,


35

Solo per aggiungere alla risposta di Ned sopra: puoi usarlo per reindirizzare l'output a qualsiasi oggetto che implementa un metodo write (str) .

Questo può essere usato con buoni risultati per "catturare" l'output stdout in un'applicazione GUI.

Ecco un esempio sciocco 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 !"

5
Funziona per me con Python 2.6 e PyQT4. Sembra strano votare il codice di lavoro quando non riesci a capire perché non funziona!
Nicolas Lefebvre,

9
non dimenticare di aggiungere anche flush ()!
Will

6

A partire da Python 2.6 è possibile utilizzare qualsiasi cosa che implementa l' TextIOBaseAPI dal modulo io in sostituzione. Questa soluzione consente anche di utilizzare sys.stdout.buffer.write()in Python 3 per scrivere (già) stringhe di byte codificate su stdout (vedere stdout in Python 3 ). L'uso StringIOnon funzionerebbe allora, perché né sys.stdout.encodingsys.stdout.buffersarebbe disponibile.

Una soluzione che utilizza 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

Questa soluzione funziona per Python 2> = 2.6 e Python 3.

Si noti che il nostro nuovo sys.stdout.write()accetta solo stringhe unicode e sys.stdout.buffer.write()accetta solo stringhe di byte. Questo potrebbe non essere il caso del vecchio codice, ma spesso è il caso del codice creato per essere eseguito su Python 2 e 3 senza modifiche, di cui spesso si avvale sys.stdout.buffer.

Puoi creare una leggera variazione che accetta stringhe di unicode e byte per 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)

Non è necessario impostare la codifica del buffer su sys.stdout.encoding, ma questo aiuta quando si utilizza questo metodo per testare / confrontare l'output dello script.


Questa risposta mi ha aiutato durante l'impostazione del parametro stdout di un oggetto Environment da utilizzare con core.py di Httpie.
Fragorl,

6

Questo metodo ripristina sys.stdout anche se esiste un'eccezione. Ottiene anche qualsiasi output prima dell'eccezione.

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

Testato in Python 2.7.10 usando io.BytesIO()

Testato in Python 3.6.4 usando io.StringIO()


Bob, aggiunto per un caso se ritieni che qualcosa della sperimentazione con codice modificato / esteso possa diventare interessante in ogni senso, altrimenti sentiti libero di cancellarlo

Ad informandum ... alcune osservazioni della sperimentazione estesa durante la ricerca di alcuni meccanismi fattibili per "afferrare" gli output, indirizzati numexpr.print_versions()direttamente al <stdout>(sulla necessità di pulire la GUI e raccogliere i dettagli nel rapporto di debug)

# 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

Un gestore di contesto per 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()

usare così:

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

4

In Python3.6, i moduli StringIOe cStringIOsono spariti, dovresti invece usarli, io.StringIOquindi dovresti farlo come la prima risposta:

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
Potresti migliorare la qualità della tua risposta spiegando come funziona il codice sopra e come questo è un miglioramento rispetto alla situazione dell'interrogatore.
toonice


1

Ecco un'altra opinione su questo. contextlib.redirect_stdoutcon io.StringIO()come documentato è fantastico, ma è ancora un po 'prolisso per l'uso quotidiano. Ecco come renderlo un one-liner effettuando la sottoclasse 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()  

Poiché __enter__ restituisce self, l'oggetto del gestore di contesto è disponibile dopo l'uscita dal blocco with. Inoltre, grazie al metodo __repr__, la rappresentazione in formato stringa dell'oggetto gestore di contesto è, di fatto, stdout. Quindi ora hai

with capture() as message:
    print('Hello World!')
print(str(message)=='Hello World!\n')  # returns True
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.