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