supongamos que hay un script que hace algo como esto:
# module writer.py
import sys
def write():
sys.stdout.write("foobar")
Ahora suponga que quiero capturar la salida de la write
función y almacenarla en una variable para su posterior procesamiento. La solución ingenua fue:
# module mymodule.py
from writer import write
out = write()
print out.upper()
Pero esto no funciona. Se me ocurre otra solución y funciona, pero por favor, avíseme si hay una mejor manera de resolver el problema. Gracias
import sys
from cStringIO import StringIO
# setup the environment
backup = sys.stdout
# ####
sys.stdout = StringIO() # capture output
write()
out = sys.stdout.getvalue() # release output
# ####
sys.stdout.close() # close the stream
sys.stdout = backup # restore original stdout
print out.upper() # post processing
value = subprocess.check_output(command, shell=True)
Aquí hay una versión de administrador de contexto de su código. Produce una lista de dos valores; el primero es stdout, el segundo es stderr.
import contextlib @contextlib.contextmanager def capture(): import sys from cStringIO import StringIO oldout,olderr = sys.stdout, sys.stderr try: out=[StringIO(), StringIO()] sys.stdout,sys.stderr = out yield out finally: sys.stdout,sys.stderr = oldout, olderr out[0] = out[0].getvalue() out[1] = out[1].getvalue() with capture() as out: print 'hi'
fuente
with capture() as out:
se comportan de manera diferente awith capture() as out, err:
subprocess
y redirige la salida a sys.stdout / stderr. Esto se debe aStringIO
que no es un objeto de archivo real y pierde lafileno()
función.Para futuros visitantes: Python 3.4 contextlib proporciona esto directamente (consulte la ayuda de Python contextlib ) a través del
redirect_stdout
administrador de contexto:from contextlib import redirect_stdout import io f = io.StringIO() with redirect_stdout(f): help(pow) s = f.getvalue()
fuente
Esta es la contraparte del decorador de mi código original.
writer.py
sigue siendo el mismo:import sys def write(): sys.stdout.write("foobar")
mymodule.py
ligeramente se modifica:from writer import write as _write from decorators import capture @capture def write(): return _write() out = write() # out post processing...
Y aquí está el decorador:
def capture(f): """ Decorator to capture standard output """ def captured(*args, **kwargs): import sys from cStringIO import StringIO # setup the environment backup = sys.stdout try: sys.stdout = StringIO() # capture output f(*args, **kwargs) out = sys.stdout.getvalue() # release output finally: sys.stdout.close() # close the stream sys.stdout = backup # restore original stdout return out # captured output wrapped in a string return captured
fuente
O tal vez use una funcionalidad que ya está allí ...
from IPython.utils.capture import capture_output with capture_output() as c: print('some output') c() print c.stdout
fuente
A partir de Python 3, también puede usar
sys.stdout.buffer.write()
para escribir (ya) cadenas de bytes codificadas en stdout (consulte stdout en Python 3 ). Cuando haces eso, elStringIO
enfoque simple no funciona porque nisys.stdout.encoding
nisys.stdout.buffer
estarían disponibles.A partir de Python 2.6, puede usar la
TextIOBase
API , que incluye los atributos que faltan:import sys from io import TextIOWrapper, BytesIO # setup the environment old_stdout = sys.stdout sys.stdout = TextIOWrapper(BytesIO(), sys.stdout.encoding) # do some writing (indirectly) write("blub") # 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 # do stuff with the output print(out.upper())
Esta solución funciona para Python 2> = 2.6 y Python 3. Tenga en cuenta que nuestro
sys.stdout.write()
solo acepta cadenas Unicode ysys.stdout.buffer.write()
solo acepta cadenas de bytes. Puede que este no sea 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.Si necesita admitir código que envía cadenas de bytes a stdout directamente sin usar stdout.buffer, puede usar esta variación:
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 sys.stdout.encoding, pero esto ayuda cuando se usa este método para probar / comparar la salida del script.
fuente
La pregunta aquí (el ejemplo de cómo redirigir la salida, no la
tee
parte) se utilizaos.dup2
para redirigir una transmisión a nivel de sistema operativo. Eso es bueno porque también se aplicará a los comandos que genere desde su programa.fuente
Creo que deberías mirar estos cuatro objetos:
from test.test_support import captured_stdout, captured_output, \ captured_stderr, captured_stdin
Ejemplo:
from writer import write with captured_stdout() as stdout: write() print stdout.getvalue().upper()
UPD: Como dijo Eric en un comentario, uno no debería usarlos directamente, así que lo copié y pegué.
# Code from test.test_support: import contextlib import sys @contextlib.contextmanager def captured_output(stream_name): """Return a context manager used by captured_stdout and captured_stdin that temporarily replaces the sys stream *stream_name* with a StringIO.""" import StringIO orig_stdout = getattr(sys, stream_name) setattr(sys, stream_name, StringIO.StringIO()) try: yield getattr(sys, stream_name) finally: setattr(sys, stream_name, orig_stdout) def captured_stdout(): """Capture the output of sys.stdout: with captured_stdout() as s: print "hello" self.assertEqual(s.getvalue(), "hello") """ return captured_output("stdout") def captured_stderr(): return captured_output("stderr") def captured_stdin(): return captured_output("stdin")
fuente
Me gusta la solución contextmanager, sin embargo, si necesita el búfer almacenado con el archivo abierto y el soporte de fileno, puede hacer algo como esto.
import six from six.moves import StringIO class FileWriteStore(object): def __init__(self, file_): self.__file__ = file_ self.__buff__ = StringIO() def __getattribute__(self, name): if name in { "write", "writelines", "get_file_value", "__file__", "__buff__"}: return super(FileWriteStore, self).__getattribute__(name) return self.__file__.__getattribute__(name) def write(self, text): if isinstance(text, six.string_types): try: self.__buff__.write(text) except: pass self.__file__.write(text) def writelines(self, lines): try: self.__buff__.writelines(lines) except: pass self.__file__.writelines(lines) def get_file_value(self): return self.__buff__.getvalue()
utilizar
import sys sys.stdout = FileWriteStore(sys.stdout) print "test" buffer = sys.stdout.get_file_value() # you don't want to print the buffer while still storing # else it will double in size every print sys.stdout = sys.stdout.__file__ print buffer
fuente
Aquí hay un administrador de contexto que se inspira en la respuesta de @ JonnyJD que admite la escritura de bytes en
buffer
atributos, pero que también aprovecha las referencias dunder-io de sys para una mayor simplificación.import io import sys import contextlib @contextlib.contextmanager def capture_output(): output = {} try: # Redirect sys.stdout = io.TextIOWrapper(io.BytesIO(), sys.stdout.encoding) sys.stderr = io.TextIOWrapper(io.BytesIO(), sys.stderr.encoding) yield output finally: # Read sys.stdout.seek(0) sys.stderr.seek(0) output['stdout'] = sys.stdout.read() output['stderr'] = sys.stderr.read() sys.stdout.close() sys.stderr.close() # Restore sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ with capture_output() as output: print('foo') sys.stderr.buffer.write(b'bar') print('stdout: {stdout}'.format(stdout=output['stdout'])) print('stderr: {stderr}'.format(stderr=output['stderr']))
La salida es:
fuente