Stdout aus einem Skript erfassen?

86

Angenommen, es gibt ein Skript, das so etwas tut:

# module writer.py
import sys

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

Angenommen, ich möchte die Ausgabe der writeFunktion erfassen und zur weiteren Verarbeitung in einer Variablen speichern. Die naive Lösung war:

# module mymodule.py
from writer import write

out = write()
print out.upper()

Das funktioniert aber nicht. Ich habe eine andere Lösung gefunden und sie funktioniert, aber bitte lassen Sie mich wissen, ob es einen besseren Weg gibt, das Problem zu lösen. Vielen Dank

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
Paolo
quelle

Antworten:

48

Das Einstellen stdoutist ein vernünftiger Weg, dies zu tun. Eine andere Möglichkeit besteht darin, es als einen anderen Prozess auszuführen:

import subprocess

proc = subprocess.Popen(["python", "-c", "import writer; writer.write()"], stdout=subprocess.PIPE)
out = proc.communicate()[0]
print out.upper()
Matthew Flaschen
quelle
4
check_output erfasst direkt die Ausgabe eines Befehls, der in einem Unterprozess ausgeführt wird: <br> value = subprocess.check_output (Befehl, Shell = True)
Arthur
Formatierte Version :value = subprocess.check_output(command, shell=True)
Nae
45

Hier ist eine Kontextmanager-Version Ihres Codes. Es ergibt eine Liste von zwei Werten; der erste ist stdout, der zweite ist 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'
Jason Mörtel
quelle
Ich liebe diese Lösung. Ich habe Änderungen vorgenommen, um nicht versehentlich Daten aus einem Stream zu verlieren, für den ich keine Ausgabe erwarte, z. B. unerwartete Fehler. In meinem Fall kann capture () sys.stderr oder sys.stdout als Parameter akzeptieren, was angibt, dass nur dieser Stream erfasst wird.
Joshua Richardson
StringIO unterstützt in keiner Weise Unicode, daher können Sie die Antwort hier integrieren, damit die oben genannten Zeichen Nicht-ASCII-Zeichen unterstützen: stackoverflow.com/a/1819009/425050
mafrosis
2
Das Ändern eines Wertes in der endgültigen ist wirklich ziemlich seltsam - with capture() as out:wird sich anders verhalten alswith capture() as out, err:
Eric
Die Unterstützung von Unicode / stdout.buffer kann mit dem io-Modul erreicht werden. Siehe meine Antwort .
JonnyJD
1
Diese Lösung subprocessfunktioniert nicht, wenn Sie die Ausgabe verwenden und an sys.stdout / stderr umleiten. Dies liegt daran, dass StringIOes sich nicht um ein echtes Dateiobjekt handelt und die fileno()Funktion fehlt .
Letmaik
44

Für zukünftige Besucher: Python 3.4 contextlib stellt dies direkt (siehe Python contextlib-Hilfe ) über den redirect_stdoutKontextmanager bereit :

from contextlib import redirect_stdout
import io

f = io.StringIO()
with redirect_stdout(f):
    help(pow)
s = f.getvalue()
noder
quelle
Dies löst das Problem nicht, wenn Sie versuchen, in sys.stdout.buffer zu schreiben (wie Sie es beim Schreiben von Bytes tun müssen). StringIO verfügt nicht über das Pufferattribut, TextIOWrapper dagegen. Siehe die Antwort von @JonnyJD.
Weber
9

Dies ist das Dekorationsgegenstück meines ursprünglichen Codes.

writer.py Bleibt das selbe:

import sys

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

mymodule.py wird leicht modifiziert:

from writer import write as _write
from decorators import capture

@capture
def write():
    return _write()

out = write()
# out post processing...

Und hier ist der Dekorateur:

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
Paolo
quelle
9

Oder nutzen Sie Funktionen, die bereits vorhanden sind ...

from IPython.utils.capture import capture_output

with capture_output() as c:
    print('some output')

c()

print c.stdout
dgrigonis
quelle
6

Ab Python 3 können Sie auch sys.stdout.buffer.write()(bereits) codierte Byte-Strings in stdout schreiben (siehe stdout in Python 3 ). Wenn Sie dies tun, StringIOfunktioniert der einfache Ansatz nicht, da weder verfügbar sys.stdout.encodingnoch sys.stdout.bufferverfügbar wäre.

Ab Python 2.6 können Sie die TextIOBaseAPI verwenden , die die fehlenden Attribute enthält:

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

Diese Lösung funktioniert für Python 2> = 2.6 und Python 3. Bitte beachten Sie, dass unsere sys.stdout.write()nur Unicode-Zeichenfolgen und akzeptiertsys.stdout.buffer.write() nur Byte-Zeichenfolgen akzeptiert. Dies ist möglicherweise nicht der Fall für alten Code, aber häufig für Code, der für die Ausführung auf Python 2 und 3 ohne Änderungen erstellt wurde.

Wenn Sie Code unterstützen müssen, der Byte-Strings direkt an stdout sendet, ohne stdout.buffer zu verwenden, können Sie diese Variante verwenden:

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)

Sie müssen die Codierung des Puffers nicht auf sys.stdout.encoding festlegen, dies ist jedoch hilfreich, wenn Sie diese Methode zum Testen / Vergleichen der Skriptausgabe verwenden.

JonnyJD
quelle
3

Die Frage hier (das Beispiel, wie die Ausgabe umgeleitet wird, nicht das teeTeil) wird verwendet os.dup2, um einen Stream auf Betriebssystemebene umzuleiten. Das ist schön, weil es auch für Befehle gilt, die Sie aus Ihrem Programm erzeugen.

Jeremiah Willcock
quelle
3

Ich denke, Sie sollten sich diese vier Objekte ansehen:

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

Beispiel:

from writer import write

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

UPD: Wie Eric in einem Kommentar sagte, sollte man sie nicht direkt verwenden, also habe ich sie kopiert und eingefügt.

# 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")
Oleksandr Fedorov
quelle
3

Ich mag die Kontextmanager-Lösung, aber wenn Sie den Puffer benötigen, der mit der Unterstützung für geöffnete Dateien und Dateien gespeichert ist, können Sie so etwas tun.

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

verwenden

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
Nathan Buckner
quelle