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
66
demandé sur D Coetzee 2011-02-28 01:49:33

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()
43
répondu Matthew Flaschen 2013-10-22 02:59:19

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'
35
répondu Jason Grout 2012-05-24 18:49:31

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()
21
répondu nodesr 2016-11-04 07:25:38

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
9
répondu dgrigonis 2016-02-25 10:18:12

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
7
répondu Paolo 2015-03-26 17:55:34

à 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.

4
répondu JonnyJD 2013-10-13 17:56:35

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.

3
répondu Jeremiah Willcock 2017-05-23 11:54:56

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")
3
répondu Alex Fedorov 2013-10-21 10:33:49

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
3
répondu Nathan Buckner 2016-10-11 15:24:40