załóżmy, że skrypt robi coś takiego:
# module writer.py
import sys
def write():
sys.stdout.write("foobar")
Załóżmy teraz, że chcę przechwycić dane wyjściowe write
funkcji i zapisać je w zmiennej do dalszego przetwarzania. Naiwnym rozwiązaniem było:
# module mymodule.py
from writer import write
out = write()
print out.upper()
Ale to nie działa. Wymyślam inne rozwiązanie i działa, ale proszę, daj mi znać, czy istnieje lepszy sposób rozwiązania problemu. Dzięki
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
value = subprocess.check_output(command, shell=True)
Oto wersja twojego kodu menedżera kontekstu. Daje listę dwóch wartości; pierwszy to stdout, drugi to 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'
źródło
with capture() as out:
będzie się zachowywać inaczej niżwith capture() as out, err:
subprocess
i przekierowujesz dane wyjściowe do sys.stdout / stderr. Dzieje się tak, ponieważStringIO
nie jest to prawdziwy obiekt pliku i brakuje mufileno()
funkcji.Dla przyszłych odwiedzających: Python 3.4 contextlib zapewnia to bezpośrednio (patrz pomoc Python contextlib ) za pośrednictwem
redirect_stdout
menedżera kontekstu:from contextlib import redirect_stdout import io f = io.StringIO() with redirect_stdout(f): help(pow) s = f.getvalue()
źródło
To jest odpowiednik dekoratora mojego oryginalnego kodu.
writer.py
pozostaje takie samo:import sys def write(): sys.stdout.write("foobar")
mymodule.py
nieznacznie zostaje zmodyfikowany:from writer import write as _write from decorators import capture @capture def write(): return _write() out = write() # out post processing...
A oto dekorator:
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
źródło
A może skorzystaj z funkcjonalności, która już tam jest ...
from IPython.utils.capture import capture_output with capture_output() as c: print('some output') c() print c.stdout
źródło
Począwszy od Pythona 3, możesz również użyć
sys.stdout.buffer.write()
do zapisania (już) zakodowanych ciągów bajtów na standardowe wyjście (zobacz stdout w Pythonie 3 ). Kiedy to robisz, prosteStringIO
podejście nie działa, ponieważ ani jedno,sys.stdout.encoding
ani drugie niesys.stdout.buffer
byłoby dostępne.Począwszy od Pythona 2.6 możesz używać
TextIOBase
API , które zawiera brakujące atrybuty: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())
To rozwiązanie działa dla Pythona 2> = 2.6 i Pythona 3. Należy pamiętać, że nasz
sys.stdout.write()
akceptuje tylko ciągi znaków Unicode isys.stdout.buffer.write()
tylko ciągi bajtów. Może się tak nie zdarzyć w przypadku starego kodu, ale często ma to miejsce w przypadku kodu, który jest zbudowany tak, aby działał w Pythonie 2 i 3 bez zmian.Jeśli potrzebujesz obsługiwać kod, który wysyła ciągi bajtów bezpośrednio na standardowe wyjście bez używania stdout.buffer, możesz użyć tej odmiany:
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)
Nie musisz ustawiać kodowania bufora sys.stdout.encoding, ale jest to pomocne, gdy używasz tej metody do testowania / porównywania danych wyjściowych skryptu.
źródło
Pytanie tutaj (przykład przekierowania wyjścia, a nie
tee
części) służyos.dup2
do przekierowania strumienia na poziomie systemu operacyjnego. To miłe, ponieważ będzie miało zastosowanie również do poleceń, które tworzysz z programu.źródło
Myślę, że powinieneś spojrzeć na te cztery obiekty:
from test.test_support import captured_stdout, captured_output, \ captured_stderr, captured_stdin
Przykład:
from writer import write with captured_stdout() as stdout: write() print stdout.getvalue().upper()
UPD: Jak powiedział Eric w komentarzu, nie powinno się ich używać bezpośrednio, więc skopiowałem i wkleiłem.
# 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")
źródło
Podoba mi się rozwiązanie Contextmanager, ale jeśli potrzebujesz bufora przechowywanego z otwartym plikiem i obsługą plików, możesz zrobić coś takiego.
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()
posługiwać się
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
źródło
Oto kierownik kontekst wzorując @ JonnyJD za odpowiedź wspierający pisanie bajtów
buffer
atrybutów przylegają również wykorzystując Dunder-io referenes SYS koszulka dla dalszego uproszczenia.import io import sys import contextlib @contextlib.contextmanager def capture_output(): output = {} try: # Redirect sys.stdout = io.TextIOWrapper(io.BytesIO(), sys.stdout.encoding) sys.stderr = io.TextIOWrapper(io.BytesIO(), sys.stderr.encoding) yield output finally: # Read sys.stdout.seek(0) sys.stderr.seek(0) output['stdout'] = sys.stdout.read() output['stderr'] = sys.stderr.read() sys.stdout.close() sys.stderr.close() # Restore sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ with capture_output() as output: print('foo') sys.stderr.buffer.write(b'bar') print('stdout: {stdout}'.format(stdout=output['stdout'])) print('stderr: {stderr}'.format(stderr=output['stderr']))
Wynik to:
źródło