Capture de stdout à partir D'un script en Python
supposons qu'il y ait un script faisant quelque chose comme ceci:
# module writer.py
import sys
def write():
sys.stdout.write("foobar")
supposons maintenant que je veuille capturer la sortie de la fonction write
et la stocker dans une variable pour un traitement ultérieur. La solution naïve était:
# module mymodule.py
from writer import write
out = write()
print out.upper()
mais ça ne marche pas. Je trouve une autre solution et ça marche, mais s'il vous plaît, faites-moi savoir s'il y a une meilleure façon de résoudre le problème. Merci
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
9 réponses
Réglage stdout
est une façon raisonnable de le faire. Un autre est de l'exécuter comme un autre processus:
import subprocess
proc = subprocess.Popen(["python", "-c", "import writer; writer.write()"], stdout=subprocess.PIPE)
out = proc.communicate()[0]
print out.upper()
Voici une version de gestionnaire de contexte de votre code. Il fournit une liste de deux valeurs; la première est stdout, la seconde est 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'
Pour les futurs visiteurs: Python 3.4 contextlib fournit directement (voir Python contextlib aide ) via le redirect_stdout
gestionnaire de contexte:
f = io.StringIO()
with redirect_stdout(f):
help(pow)
s = f.getvalue()
ou peut-être utiliser une fonctionnalité qui existe déjà...
from IPython.utils.capture import capture_output
with capture_output() as c:
print('some output')
c()
print c.stdout
c'est le pendant décorateur de mon code d'origine.
writer.py
reste le même:
import sys
def write():
sys.stdout.write("foobar")
mymodule.py
légèrement se modifie:
from writer import write as _write
from decorators import capture
@capture
def write():
return _write()
out = write()
# out post processing...
et voici le décorateur:
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
à partir de Python 3, vous pouvez aussi utiliser sys.stdout.buffer.write()
pour écrire (déjà) des chaînes d'octets encodées vers stdout (voir stdout en Python 3 ).
Lorsque vous faites cela, l'approche simple StringIO
ne fonctionne pas parce que ni sys.stdout.encoding
ni sys.stdout.buffer
ne serait disponible.
à partir de Python 2.6 vous pouvez utiliser le TextIOBase
API , qui inclut les attributs manquants:
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())
cette solution fonctionne pour Python 2 >= 2.6 et Python 3.
Veuillez noter que notre sys.stdout.write()
n'accepte que les chaînes unicode et sys.stdout.buffer.write()
n'accepte que les chaînes byte.
Ce n'est peut-être pas le cas pour l'ancien code, mais c'est souvent le cas pour le code qui est construit pour fonctionner sur Python 2 et 3 sans modifications.
si vous avez besoin de supporter le code qui envoie des chaînes d'octets directement à stdout sans utiliser stdout.tampon, vous pouvez utiliser cette variation:
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)
vous n'avez pas à configurer l'encodage du buffer le sys.la sortie standard stdout.encodage, mais cela aide lors de l'utilisation de cette méthode pour tester/comparer la sortie du script.
la question ici (l'exemple de comment rediriger la sortie, pas la partie tee
) utilise os.dup2
pour rediriger un flux au niveau du système D'exploitation. C'est agréable parce qu'il s'appliqueront aux commandes que vous frayer à partir de votre programme.
je pense que Vous devriez regarder ces quatre objets:
from test.test_support import captured_stdout, captured_output, \
captured_stderr, captured_stdin
exemple:
from writer import write
with captured_stdout() as stdout:
write()
print stdout.getvalue().upper()
UPD: comme Eric l'a dit dans un commentaire, on ne devrait pas les utiliser directement, donc je l'ai copié et collé.
# 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")
j'aime la solution contextmanager mais si vous avez besoin du tampon stocké avec le fichier ouvert et le support fileno, vous pouvez faire quelque chose comme ça.
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()
utiliser
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