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


112

Я использую библиотеку 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()

?



Мой ответ в связанном вопросе применим и здесь.
Мартейн Питерс

Я попытался сделать это один раз, и лучший ответ, который я нашел, был: stackoverflow.com/a/3113913/1330293
elyase

Связанный ответ @elyase - элегантное решение
Пайклер

Смотрите этот ответ .
Мартино

Ответы:


186

Попробуйте этот диспетчер контекста:

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список, а также диспетчер контекста, возможно, более удобны).


Спасибо! И спасибо за добавление расширенного раздела ... Изначально я использовал назначение срезов, чтобы вставить захваченный текст в список, затем я ударил себя по голове и использовал .extend()вместо этого, чтобы его можно было использовать конкатентивно, как вы заметили. :-)
kindall

PS Если он будет использоваться повторно, я бы предложил добавить self._stringio.truncate(0)после self.extend()вызова __exit__()метода, чтобы освободить часть памяти, удерживаемой _stringioучастником.
Мартино

25
Отличный ответ, спасибо. Для Python 3 используйте from io import StringIOвместо первой строки в диспетчере контекста.
Wtower

1
Это потокобезопасно? Что произойдет, если какой-то другой поток / вызов использует print () во время выполнения do_something?
Derorrist

1
Этот ответ не будет работать для вывода из общих библиотек C, вместо этого см. Этот ответ .
craymichael

82

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

Этот инструмент добавляет гибкости существующим функциям или классам, вывод которых жестко привязан к 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 означает, что этот диспетчер контекста не подходит для использования в коде библиотеки и в большинстве многопоточных приложений. Это также не влияет на вывод подпроцессов. Тем не менее, это все еще полезный подход для многих служебных скриптов.

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


когда попробовал f = io.StringIO() with redirect_stdout(f): logger = getLogger('test_logger') logger.debug('Test debug message') out = f.getvalue() self.assertEqual(out, 'DEBUG:test_logger:Test debug message'). AssertionError: '' != 'Test debug message'
Выдает

Это означает, что я сделал что-то не так, или он не смог поймать журнал stdout.
Эзиз Дурдыев

@EzizDurdyyev, logger.debugпо умолчанию не пишет в stdout. Если вы замените вызов журнала на, print()вы должны увидеть сообщение.
ForeverWintr

Да, я знаю, но я сделать это написать на стандартный вывод так: stream_handler = logging.StreamHandler(sys.stdout). И добавьте этот обработчик в мой регистратор. поэтому он должен писать в stdout иredirect_stdout ловить его, верно?
Эзиз Дурдыев

Я подозреваю, что проблема в том, как вы настроили регистратор. Я бы проверил, что он выводится на стандартный вывод без redirect_stdout. Если это так, возможно, буфер не очищается до выхода из диспетчера контекста.
ForeverWintr

0

Вот асинхронное решение с использованием файловых каналов.

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()
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.