¿Puedo redirigir el stdout en python a algún tipo de buffer de cadena?


138

Estoy usando Python ftplibpara escribir un pequeño cliente FTP, pero algunas de las funciones en el paquete no devuelven la salida de la cadena, sino que imprimen en stdout. Quiero redirigir stdouta un objeto del que pueda leer la salida.

Sé que stdoutse puede redirigir a cualquier archivo normal con:

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

Pero prefiero un método que no use el disco local.

Estoy buscando algo como BufferedReaderen Java que se pueda usar para envolver un búfer en una secuencia.


No creo que stdout = open("file", "a")por sí solo redirija nada.
Alexey

Respuestas:


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, no necesita mantener una referencia al stdoutobjeto original , ya que siempre está disponible en sys.__stdout__. Ver docs.python.org/library/sys.html#sys.__stdout__ .
Ayman Hourieh

92
Bueno, ese es un debate interesante. El stdout original absoluto está disponible, pero cuando se reemplaza de esta manera, es mejor usar un guardado explícito como lo he hecho, ya que alguien más podría haber reemplazado stdout y si usa stdout , podría cambiar su reemplazo.
Ned Batchelder

55
¿Esta operación en un hilo alteraría el comportamiento de otros hilos? Quiero decir, ¿es seguro?
Anuvrat Parashar

66
Recomiendo reasignar el stdout antiguo en un finally:bloque, por lo que también se reasigna si se produce una excepción en el medio. try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn

20
Si desea usar esto en Python 3, reemplace cStringIO con io.
Anthony Labarre


35

Solo para agregar a la respuesta de Ned anterior: puede usar esto para redirigir la salida a cualquier objeto que implemente un método de escritura (str) .

Esto se puede usar con buenos resultados para "capturar" la salida estándar en una aplicación GUI.

Aquí hay un ejemplo tonto en 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 !"

55
Funciona para mí con python 2.6 y PyQT4. Parece extraño rechazar el código de trabajo cuando no se puede decir por qué no funciona.
Nicolas Lefebvre

9
¡no olvides agregar flush () también!
Will

6

Comenzando con Python 2.6, puede usar cualquier cosa que implemente la TextIOBaseAPI desde el módulo io como reemplazo. Esta solución también le permite usar sys.stdout.buffer.write()en Python 3 para escribir (ya) cadenas de bytes codificadas en stdout (ver stdout en Python 3 ). Usar StringIOno funcionaría entonces, porque ni sys.stdout.encodingtampocosys.stdout.buffer estaría disponible.

Una solución usando 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

Esta solución funciona para Python 2> = 2.6 y Python 3.

Tenga en cuenta que nuestro nuevo sys.stdout.write()solo acepta cadenas Unicode y sys.stdout.buffer.write()solo acepta cadenas de bytes. Es posible que este no sea el caso del código antiguo, pero a menudo es el caso del código que está construido para ejecutarse en Python 2 y 3 sin cambios, lo que a menudo hace uso de él sys.stdout.buffer.

Puede crear una ligera variación que acepte cadenas unicode y de bytes para 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)

No es necesario establecer la codificación del búfer en sys.stdout.encoding, pero esto ayuda cuando se utiliza este método para probar / comparar la salida del script.


Esta respuesta me ayudó cuando configuré el parámetro stdout de un objeto Environment para usarlo con core.py de Httpie.
fragorl

6

Este método restaura sys.stdout incluso si hay una excepción. También obtiene cualquier salida antes de la excepción.

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

Probado en Python 2.7.10 usando io.BytesIO()

Probado en Python 3.6.4 usando io.StringIO()


Bob, agregado para un caso si siente que algo de la experimentación de código modificado / extendido puede ser interesante en algún sentido, de lo contrario, siéntase libre de eliminarlo

Ad informandum ... algunas observaciones de la experimentación extendida durante la búsqueda de algunas mecánicas viables para "agarrar" salidas, dirigidas numexpr.print_versions()directamente al <stdout>(ante la necesidad de limpiar la GUI y recopilar detalles en el informe de depuración)

# 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 administrador de contexto para 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()

usar así:

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

4

En Python3.6, los módulos StringIOy cStringIOse han ido, debe usarlos en su io.StringIOlugar, por lo que debe hacer esto como la primera respuesta:

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
Puede mejorar la calidad de su respuesta explicando cómo funciona el código anterior y cómo esto es una mejora con respecto a la situación del interlocutor.
toonice


1

Aquí hay otra versión de esto. contextlib.redirect_stdoutcon io.StringIO()lo documentado es genial, pero sigue siendo un poco detallado para el uso diario. A continuación, le mostramos cómo hacerlo de una sola línea subclasificando 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()  

Como __enter__ devuelve self, tiene el objeto de administrador de contexto disponible después de que sale el bloque with. Además, gracias al método __repr__, la representación de cadena del objeto del administrador de contexto es, de hecho, stdout. Así que ahora tienes

with capture() as message:
    print('Hello World!')
print(str(message)=='Hello World!\n')  # returns True
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.