Как захватить вывод stdout из вызова функции Python?
Я использую библиотеку Python, которая делает что-то с объектом
do_something(my_object)
и меняет это. При этом он выводит некоторую статистику на стандартный вывод, и я хотел бы получить контроль над этой информацией. Правильное решение было бы изменить do_something()
вернуть соответствующую информацию,
out = do_something(my_object)
но это будет некоторое время, прежде чем разработчики do_something()
добраться до этого вопроса. В качестве обходного пути я подумал о разборе чего угодно do_something()
пишет в стандартный вывод.
Как я могу захватить вывод stdout между двумя точками в коде, например,
start_capturing()
do_something(my_object)
out = end_capturing()
?
5 ответов
Попробуйте этот контекстный менеджер:
from cStringIO import StringIO
import sys
class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stdout = self._stdout
Использование:
with Capturing() as output:
do_something(my_object)
output
теперь список, содержащий строки, напечатанные вызовом функции.
Расширенное использование:
Что может быть неочевидным, так это то, что это можно сделать несколько раз и результаты объединены:
with Capturing() as output:
print 'hello world'
print 'displays on screen'
with Capturing(output) as output: # note the constructor argument
print 'hello world2'
print 'done'
print 'output:', output
Выход:
displays on screen
done
output: ['hello world', 'hello world2']
Обновление: они добавили redirect_stdout()
в contextlib
в Python 3.4 (вместе с redirect_stderr()
). Так что вы могли бы использовать io.StringIO
с тем, чтобы достичь аналогичного результата (хотя Capturing
быть списком, а также контекстным менеджером, возможно, более удобно).
В python >= 3.4, contextlib содержит redirect_stdout
декоратор. Его можно использовать для ответа на ваш вопрос следующим образом:
import io
from contextlib import redirect_stdout
f = io.StringIO()
with redirect_stdout(f):
do_something(my_object)
out = f.getvalue()
Из документов:
Диспетчер контекста для временного перенаправления sys.stdout в другой файл или файловый объект.
Этот инструмент добавляет гибкость существующим функциям или классам, чьи выходные данные жестко привязаны к стандартному выводу.
Например, вывод help() обычно отправляется в sys.stdout. Вы можете записать этот вывод в строку, перенаправив вывод в объект io.StringIO:
f = io.StringIO() with redirect_stdout(f): help(pow) s = f.getvalue()
Чтобы отправить вывод help() в файл на диске, перенаправьте вывод в обычный файл:
with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow)
Чтобы отправить вывод help() в sys.stderr:
with redirect_stdout(sys.stderr): help(pow)
Обратите внимание, что глобальный побочный эффект для sys.stdout означает, что этот менеджер контекста не подходит для использования в коде библиотеки и большинстве многопоточных приложений. Это также не влияет на вывод подпроцессов. Тем не менее, это все еще полезный подход для многих служебных скриптов.
Этот контекстный менеджер является реентерабельным.
Вот асинхронное решение с использованием файловых каналов.
import threading
import sys
import os
class Capturing():
def __init__(self):
self._stdout = None
self._stderr = None
self._r = None
self._w = None
self._thread = None
self._on_readline_cb = None
def _handler(self):
while not self._w.closed:
try:
while True:
line = self._r.readline()
if len(line) == 0: break
if self._on_readline_cb: self._on_readline_cb(line)
except:
break
def print(self, s, end=""):
print(s, file=self._stdout, end=end)
def on_readline(self, callback):
self._on_readline_cb = callback
def start(self):
self._stdout = sys.stdout
self._stderr = sys.stderr
r, w = os.pipe()
r, w = os.fdopen(r, 'r'), os.fdopen(w, 'w', 1)
self._r = r
self._w = w
sys.stdout = self._w
sys.stderr = self._w
self._thread = threading.Thread(target=self._handler)
self._thread.start()
def stop(self):
self._w.close()
if self._thread: self._thread.join()
self._r.close()
sys.stdout = self._stdout
sys.stderr = self._stderr
Пример использования:
from Capturing import *
import time
capturing = Capturing()
def on_read(line):
# do something with the line
capturing.print("got line: "+line)
capturing.on_readline(on_read)
capturing.start()
print("hello 1")
time.sleep(1)
print("hello 2")
time.sleep(1)
print("hello 3")
capturing.stop()
Кроме того, опираясь на ответы @kindall и @ForeveWintr, вот класс, который выполняет это. Основное отличие от предыдущих ответов заключается в том, что это фиксирует его как строку , а не как
StringIO
объект, с которым гораздо удобнее работать!
import io
from collections import UserString
from contextlib import redirect_stdout
class capture(UserString, str, redirect_stdout):
'''
Captures stdout (e.g., from ``print()``) as a variable.
Based on ``contextlib.redirect_stdout``, but saves the user the trouble of
defining and reading from an IO stream. Useful for testing the output of functions
that are supposed to print certain output.
'''
def __init__(self, seq='', *args, **kwargs):
self._io = io.StringIO()
UserString.__init__(self, seq=seq, *args, **kwargs)
redirect_stdout.__init__(self, self._io)
return
def __enter__(self, *args, **kwargs):
redirect_stdout.__enter__(self, *args, **kwargs)
return self
def __exit__(self, *args, **kwargs):
self.data += self._io.getvalue()
redirect_stdout.__exit__(self, *args, **kwargs)
return
def start(self):
self.__enter__()
return self
def stop(self):
self.__exit__(None, None, None)
return
Примеры:
# Using with...as
with capture() as txt1:
print('Assign these lines')
print('to a variable')
# Using start()...stop()
txt2 = capture().start()
print('This works')
print('the same way')
txt2.stop()
print('Saved in txt1:')
print(txt1)
print('Saved in txt2:')
print(txt2)
Это реализовано в Sciris как sc.capture() .
На основе
kindall
а также
ForeverWintr
ответ.
я создаю
redirect_stdout
функция для
Python<3.4
:
import io
from contextlib import contextmanager
@contextmanager
def redirect_stdout(f):
try:
_stdout = sys.stdout
sys.stdout = f
yield
finally:
sys.stdout = _stdout
f = io.StringIO()
with redirect_stdout(f):
do_something()
out = f.getvalue()