Захватить стандартный вывод из скрипта на Python
предположим, что есть скрипт, делающий что-то вроде этого:
# 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
9 ответов:
задание
stdout
- это разумный способ сделать это. Другой-запустить его как другой процесс:import subprocess proc = subprocess.Popen(["python", "-c", "import writer; writer.write()"], stdout=subprocess.PIPE) out = proc.communicate()[0] print out.upper()
вот версия контекстного менеджера вашего кода. Он дает список из двух значений; первый-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'
для будущих посетителей: Python 3.4 contextlib обеспечивает это напрямую (см. Python contextlib help) через
redirect_stdout
контекст-менеджера:f = io.StringIO() with redirect_stdout(f): help(pow) s = f.getvalue()
или может использовать функциональность, которая уже есть...
from IPython.utils.capture import capture_output with capture_output() as c: print('some output') c() print c.stdout
это декоратор аналог моего исходного кода.
writer.py
остается то же самое:import sys def write(): sys.stdout.write("foobar")
mymodule.py
sligthly модифицируется: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
начиная с 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()
принимает только строки в юникоде иsys.stdout.buffer.write()
принимает только строки байтов. Это может быть не так для старого кода, но часто бывает так для кода, который построен для работы на Python 2 и 3 без изменений.Если вам нужно поддерживать код, который отправляет байт строки в stdout напрямую без использования консоли.буфер, вы можете использовать этот вариант:
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.стандартный вывод.кодирование, но это помогает при использовании этого метод для тестирования / сравнения вывода скрипта.
вопрос здесь (пример как перенаправить вывод, а не
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