variable values register example python stdout sys

values - Capture stdout de un script en Python



ansible task status (9)

supongamos que hay un script que hace algo como esto:

# module writer.py import sys def write(): sys.stdout.write("foobar")

Ahora supongamos que quiero capturar la salida de la función de write 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 ocurrió 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


Aquí hay una versión de administrador de contexto de su código. Da 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''


Comenzando con Python 3 también puede usar sys.stdout.buffer.write() para escribir (ya) cadenas de bytes codificadas en stdout (vea stdout en Python 3 ). Cuando haces eso, el enfoque simple de StringIO no funciona porque ni sys.stdout.encoding ni sys.stdout.buffer estarían disponibles.

Comenzando con Python 2.6 puede usar la API TextIOBase , 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 de caracteres unicode y sys.stdout.buffer.write() solo acepta cadenas de bytes. Este podría no ser el caso para el código antiguo, pero a menudo es el caso para el código que está diseñado para ejecutarse en Python 2 y 3 sin cambios.

Si necesita admitir código que envíe 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 configurar la codificación del búfer en sys.stdout.encoding, pero esto ayuda cuando se utiliza este método para probar / comparar el resultado del script.


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


Esta es la contraparte decoradora de mi código original.

writer.py sigue siendo el mismo:

import sys def write(): sys.stdout.write("foobar")

mymodule.py sligthly 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


Establecer stdout es una forma razonable de hacerlo. Otra es ejecutarlo como otro proceso:

import subprocess proc = subprocess.Popen(["python", "-c", "import writer; writer.write()"], stdout=subprocess.PIPE) out = proc.communicate()[0] print out.upper()


La pregunta here (el ejemplo de cómo redirigir el resultado, no la parte del tee ) usa os.dup2 para redirigir un flujo en el nivel del sistema operativo. Eso es bueno porque se aplicará a los comandos que generes de tu programa también.


O tal vez usar la funcionalidad que ya está allí ...

from IPython.utils.capture import capture_output with capture_output() as c: print(''some output'') c() print c.stdout


Para visitantes futuros: Python 3.4 contextlib proporciona esto directamente (ver la ayuda contextual de Python ) a través del administrador de contexto redirect_stdout :

f = io.StringIO() with redirect_stdout(f): help(pow) s = f.getvalue()


Sin embargo, me gusta la solución contextmanager. Si necesita el almacenamiento intermedio almacenado con el archivo abierto y la compatibilidad con el archivo, 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