Cattura stdout da uno script in Python

supponiamo che ci sia uno script che fa qualcosa di simile a questo:

# module writer.py
import sys

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

Ora supponiamo che io voglia catturare l’output del write funzione e memorizzarlo in una variabile per l’ulteriore elaborazione. L’ingenuo soluzione è stata:

# module mymodule.py
from writer import write

out = write()
print out.upper()

Ma questo non funziona. Mi è venuta in mente un’altra soluzione e funziona, ma per favore, fatemi sapere se c’è un modo migliore per risolvere il problema. Grazie

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
InformationsquelleAutor Paolo | 2011-02-27



9 Replies
  1. 44

    Impostazione stdout è un modo ragionevole per farlo. Un altro è per l’esecuzione di un altro processo:

    import subprocess
    
    proc = subprocess.Popen(["python", "-c", "import writer; writer.write()"], stdout=subprocess.PIPE)
    out = proc.communicate()[0]
    print out.upper()
    • check_output direttamente cattura l’output di un comando in un sottoprocesso: <br> value = subprocess.check_output( comando, shell=True)
    • versione Formattata: value = subprocess.check_output(command, shell=True)
  2. 41

    Qui è un contesto manager versione del codice. Se ne ricava un elenco di due valori: il primo è stdout, il secondo è 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'
    • Amore questa soluzione. Ho modificato, in modo da non accidentalmente perdere la roba da un flusso in cui io non sono in attesa di uscita, ad esempio, di errori imprevisti. Nel mio caso, di acquisizione() può accettare sys.stderr o sys.stdout come parametro, che indica per catturare solo il flusso.
    • StringIO non supporta unicode in alcun modo, quindi, è possibile integrare la risposta qui a fare sopra il supporto non-ASCII caratteri: stackoverflow.com/a/1819009/425050
    • Modifica di una ceduto valore, infine, è piuttosto strano – with capture() as out: si comportano in modo diverso a with capture() as out, err:
    • Unicode / stdout.buffer di sostegno può essere raggiunto con l’utilizzo di i / o del modulo. Vedere la mia risposta.
    • Questa soluzione si rompe se si utilizza subprocess e reindirizzare l’output sys.stdout/stderr. Questo perché StringIO non è un vero e proprio oggetto file e manca l’ fileno() funzione.
  3. 31

    Per i futuri visitatori: Python 3.4 contextlib fornisce direttamente (vedi Python contextlib aiuto) via redirect_stdout contesto manager:

    from contextlib import redirect_stdout
    import io
    
    f = io.StringIO()
    with redirect_stdout(f):
        help(pow)
    s = f.getvalue()
    • Questo non risolve il problema quando si cerca di scrivere sys.stdout.buffer (come avete bisogno di fare durante la scrittura di byte). StringIO non hanno l’attributo buffer, mentre TextIOWrapper fa. Vedi la risposta di @JonnyJD.
  4. 10

    O forse utilizzare una funzionalità che è già lì…

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

    Questo è il decoratore contropartita del mio codice originale.

    writer.py rimane la stessa:

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

    mymodule.py leggermente viene modificato:

    from writer import write as _write
    from decorators import capture
    
    @capture
    def write():
        return _write()
    
    out = write()
    # out post processing...

    E qui è il decoratore:

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

    Di iniziare con Python 3 si può anche usare sys.stdout.buffer.write() scrivere (già) codificato stringhe di byte su stdout (vedi stdout in Python 3).
    Quando lo si fa, il semplice StringIO approccio non funziona, perché né sys.stdout.encodingsys.stdout.buffer sarebbe disponibile.

    Di iniziare con Python 2.6 è possibile utilizzare il TextIOBase API, che comprende gli attributi mancanti:

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

    Questa soluzione funziona per Python 2 >= 2.6 e Python 3.
    Siete pregati di notare che il nostro sys.stdout.write() accetta solo stringhe unicode e sys.stdout.buffer.write() accetta solo stringhe di byte.
    Questo potrebbe non essere il caso per il vecchio codice, ma è spesso il caso per il codice che è costruito per funzionare su Python 2 e 3, senza modifiche.

    Se è necessario il supporto di codice per l’invio di stringhe di byte su stdout direttamente senza l’utilizzo di stdout.buffer, è possibile utilizzare questa variante:

    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)

    Non è necessario impostare la codifica del buffer sys.stdout.la codifica, ma questo aiuta quando si utilizza questo metodo per il test/confronto output dello script.

  7. 3

    La domanda qui (l’esempio di come reindirizzare l’output, non tee parte) utilizza os.dup2 per reindirizzare un flusso di a livello di sistema operativo. Che è bello perché si applica ai comandi generato dal programma.

  8. 3

    Penso che Si dovrebbe guardare a questi quattro oggetti:

    from test.test_support import captured_stdout, captured_output, \
        captured_stderr, captured_stdin

    Esempio:

    from writer import write
    
    with captured_stdout() as stdout:
        write()
    print stdout.getvalue().upper()

    UPD: Come Eric ha detto in un commento, uno non dovrebbe usare direttamente, così ho copiato e incollato.

    # 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")
  9. 3

    Mi piace il contextmanager soluzione, tuttavia, se avete bisogno di buffer memorizzato con il numero di file aperti e fileno supporto si potrebbe fare qualcosa di simile a questo.

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

    utilizzare

    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

Lascia un commento