¿Puedo redirigir la salida estándar en python a algún tipo de búfer de cadena?

Estoy usando ftplib de ftplib para escribir un pequeño cliente FTP, pero algunas de las funciones del paquete no devuelven el resultado de la cadena, sino que se imprimen en la salida stdout . Quiero redireccionar la salida stdout a un objeto desde el cual podré leer la salida.

Sé que stdout puede ser redirigido a cualquier archivo regular con:

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

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

Estoy buscando algo como el BufferedReader en Java que se puede usar para envolver un búfer en una secuencia.

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

Hay una función contextlib.redirect_stdout () en Python 3.4:

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

Este es un ejemplo de código que muestra cómo implementarlo en versiones anteriores de Python .

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 para “capturar” la salida de 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 !" 

A partir de Python 2.6 puede usar cualquier cosa que implemente la API de TextIOBase 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 (vea stdout en Python 3 ). Usar StringIO no funcionaría entonces, porque ni sys.stdout.encoding ni sys.stdout.buffer estarían disponibles.

Una solución utilizando 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 # restre 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. Este podría no ser el caso del código antiguo, pero a menudo es el caso del código creado para ejecutarse en Python 2 y 3 sin cambios, que a menudo utiliza sys.stdout.buffer .

Puede crear una ligera variación que acepte cadenas de bytes y unicode 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 tiene que configurar la encoding del búfer de sys.stdout.encoding, pero esto ayuda cuando se usa este método para probar / comparar la salida del script.

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 cree que algo de la experimentación con el código modificado / extendido puede ser interesante en cualquier sentido, de lo contrario siéntase libre de eliminarlo

Ad informandum … algunas observaciones de la experimentación extendida durante la búsqueda de algunos mecánicos viables para “agarrar” las salidas, dirigidas por numexpr.print_versions() directamente a (sobre la necesidad de limpiar la GUI y recostackr 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  ( store to REAL_ ) fake_stdout = io.BytesIO() # .DEF FAKE_ try: # FUSED .TRY: sys.stdout.flush() # .flush() before sys.stdout = fake_stdout # .SET  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 ->  #>>> pass; sys.stdout.fileno()-> Traceback (most recent call last): # File "", line 1, in  # io.UnsupportedOperation: fileno # ? BUT REFUSES TO USE IT #''' finally: # == FINALLY: sys.stdout.flush() # .flush() before ret'd back REAL_ sys.stdout = real_stdout # .SET  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() # .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 "", line 9, in  io.UnsupportedOperation: fileno ''' 

En Python3.6, los módulos StringIO y cStringIO se han ido, debes usar io.StringIO lugar. Así que debes 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() 

Use pipe() y escriba en el descriptor de archivo apropiado.

https://docs.python.org/library/os.html#file-descriptor-operations

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

utilizar así:

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