предположим, что есть сценарий, делающий что-то вроде этого:
# module writer.py
import sys
def write():
sys.stdout.write("foobar")
Теперь предположим, что я хочу зафиксировать вывод write
функции и сохранить его в переменной для дальнейшей обработки. Наивное решение было:
# module mymodule.py
from writer import write
out = write()
print out.upper()
Но это не работает. Я придумал другое решение, и оно работает, но, пожалуйста, дайте мне знать, есть ли лучший способ решить проблему. Благодарность
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)
Вот версия менеджера контекста вашего кода. Он дает список из двух значений; первый - stdout, второй - 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'
источник
with capture() as out:
будет вести себя иначе, чемwith capture() as out, err:
subprocess
и перенаправляете вывод на sys.stdout / stderr. Это потому, чтоStringIO
это не настоящий файловый объект, иfileno()
функция отсутствует.Для будущих посетителей: Python 3.4 contextlib обеспечивает это напрямую (см. Справку Python contextlib ) через
redirect_stdout
диспетчер контекста:from contextlib import redirect_stdout import io f = io.StringIO() with redirect_stdout(f): help(pow) s = f.getvalue()
источник
Это аналог декоратора моего исходного кода.
writer.py
остается такой же:import sys def write(): sys.stdout.write("foobar")
mymodule.py
слегка модифицируется:from writer import write as _write from decorators import capture @capture def write(): return _write() out = write() # out post processing...
А вот декоратор:
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
источник
Или, может быть, использовать функциональность, которая уже есть ...
from IPython.utils.capture import capture_output with capture_output() as c: print('some output') c() print c.stdout
источник
Начиная с Python 3, вы также можете использовать
sys.stdout.buffer.write()
для записи (уже) закодированных байтовых строк в stdout (см. Stdout в Python 3 ). Когда вы это сделаете, простойStringIO
подход не сработает, потому что ни то,sys.stdout.encoding
ни другое неsys.stdout.buffer
будет доступно.Начиная с Python 2.6 вы можете использовать
TextIOBase
API , который включает недостающие атрибуты: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())
Это решение работает для Python 2> = 2.6 и Python 3. Обратите внимание, что наша программа
sys.stdout.write()
принимает только строки Unicode иsys.stdout.buffer.write()
только байтовые строки. Это может быть не так для старого кода, но часто так бывает для кода, который создан для работы на Python 2 и 3 без изменений.Если вам нужно поддерживать код, который отправляет байтовые строки в stdout напрямую, без использования stdout.buffer, вы можете использовать этот вариант:
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)
Вам не нужно устанавливать кодировку буфера sys.stdout.encoding, но это помогает при использовании этого метода для тестирования / сравнения вывода скрипта.
источник
Вопрос здесь (пример того, как перенаправить вывод, а не
tee
часть) используетos.dup2
для перенаправления потока на уровне ОС. Это хорошо, потому что это применимо и к командам, которые вы запускаете из своей программы.источник
Я думаю, Вам следует взглянуть на эти четыре объекта:
from test.test_support import captured_stdout, captured_output, \ captured_stderr, captured_stdin
Пример:
from writer import write with captured_stdout() as stdout: write() print stdout.getvalue().upper()
UPD: Как сказал Эрик в комментарии, их нельзя использовать напрямую, поэтому я скопировал и вставил их.
# 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")
источник
Мне нравится решение contextmanager, однако, если вам нужен буфер, хранящийся в открытом файле, и поддержка fileno, вы можете сделать что-то вроде этого.
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()
использовать
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
источник
Вот контекст менеджер берет вдохновение от @ JonnyJD в ответ поддерживает запись байтов к
buffer
атрибутам примыкают также воспользовавшись Dunder-ю referenes SYS, для дальнейшего упрощения.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']))
Выход:
источник