Как захватить вывод stdout из вызова функции Python?

Я использую библиотеку Python, которая что-то делает для объекта

do_something(my_object)

и изменяет его. При этом он печатает некоторые статистические данные для stdout, и я хотел бы получить доступ к этой информации. Правильным решением было бы изменить do_something(), чтобы вернуть соответствующую информацию,

out = do_something(my_object)

но до того, как разработчики do_something() получат эту проблему, пройдет некоторое время. В качестве обходного пути я подумал о том, чтобы разбор всех do_something() записывался в stdout.

Как я могу записать вывод stdout между двумя точками в коде, например,

start_capturing()
do_something(my_object)
out = end_capturing()

?

Ответ 1

Попробуйте этот контекстный менеджер:

# import StringIO for Python 2 or 3
try:
    from StringIO import StringIO
except ImportError:
    from io 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 как список, так и контекстный менеджер).

Ответ 2

В 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 в другой файл или файл-подобный объект.

Этот инструмент добавляет гибкость существующим функциям или классам, выход жестко подключен к стандартным выводам.

Например, вывод справки() обычно отправляется на sys.stdout. Вы может захватить этот вывод в строке путем перенаправления вывода на Объект io.StringIO:

  f = io.StringIO() 
  with redirect_stdout(f):
      help(pow) 
  s = f.getvalue()

Чтобы отправить вывод справки() в файл на диске, перенаправьте вывод на обычный файл:

 with open('help.txt', 'w') as f:
     with redirect_stdout(f):
         help(pow)

Чтобы отправить вывод справки() в sys.stderr:

with redirect_stdout(sys.stderr):
    help(pow)

Обратите внимание, что глобальный побочный эффект на sys.stdout означает, что этот контекст менеджер не подходит для использования в библиотечном коде и в большинстве потоков Приложения. Он также не влияет на вывод подпроцессов. Тем не менее, это по-прежнему полезный подход для многих сценариев использования.

Этот менеджер контекста является реентерабельным.