Перенаправить печать в список строк?

Я знаю, как перенаправить печать в файл.

import sys

orig_stdout = sys.stdout
f = file('out.txt', 'w')
sys.stdout = f

for i in range(2):
    print ('i = ', i)

sys.stdout = orig_stdout
f.close()

Мне нужно сделать то же самое, но без файла: сохранить вывод печати в списке строк. Как это сделать в Py3k?

Изменить: у меня могут быть сторонние отпечатки в средней части, а не мои собственные отпечатки, поэтому код должен быть универсальным для обычного "print()".

Ответ 1

import sys
class ListStream:
    def __init__(self):
        self.data = []
    def write(self, s):
        self.data.append(s)

sys.stdout = x = ListStream()

for i in range(2):
    print ('i = ', i)

sys.stdout = sys.__stdout__
print(x.data)

дает

['i = ', ' ', '0', '\n', 'i = ', ' ', '1', '\n']

Совет. Вам не нужно сохранять оригинал sys.stdout

orig_stdout = sys.stdout

так как sys.stdout может быть reset с

sys.stdout = sys.__stdout__

Вы также можете добавить синтаксический сахар, сделав ListStream контекстманером:

import sys
class ListStream:
    def __init__(self):
        self.data = []
    def write(self, s):
        self.data.append(s)
    def __enter__(self):
        sys.stdout = self
        return self
    def __exit__(self, ext_type, exc_value, traceback):
        sys.stdout = sys.__stdout__  

Добавив методы __enter__ и __exit__, вы можете теперь использовать ListStream в with-statement, который будет автоматически reset sys.stdout для вас, когда Python выйдет из with-suite:

with ListStream() as x:
    for i in range(2):
        print ('i = ', i)

print(x.data)

Ответ 2

Вместо того, чтобы загружать свой собственный класс, я думаю, что проще всего заменить sys.stdout (который является просто TextIOWrapper) с экземпляром StringIO, на который вы ссылаетесь:

import sys
from io import StringIO

s = StringIO()

sys.stdout = s

print('yo')

print('this is stuff')

print('hi')

s.getvalue()
Out[38]: 'yo\nthis is stuff\nhi\n'

s.getvalue().splitlines()
Out[39]: ['yo', 'this is stuff', 'hi']

Как говорит @unutbu, вы можете восстановить исходный stdout с помощью sys.stdout = sys.__stdout__; Мне особенно нравится идея использования диспетчера контекстов для временного перенаправления stdout туда, куда вы хотите.

Ответ 3

Я бы написал функцию, чтобы сделать это для вас, вместо того, чтобы пытаться перенаправить stdout в список (который, как я думаю, не может работать в любом случае, но не цитируйте меня на этом).

def lprint(text):
    global string_list
    try: string_list.append(text)
    except NameError as e:
        string_list = [text]

for i in range(2):
    lprint ("i = {}".format(i))

print(string_list)
[OUT]: ["i = 0","i = 1"]

Ответ 4

Это то, что я часто делаю, когда мне нужно создать приложение ncurses:

import sys

# in this wrapper class you can use a string list instead of a full string like I'm doing
class StdOutWrapper:
    lines = []
    def write(self,txt):
        self.lines.append(txt)

    # here is a method so you can get stuff out of your wrapper class
    # I am rebuilding the text, but you can do whatever you want!
    def get_text(self,beg,end):
        return '\n'.join(self.lines)

mystdout = StdOutWrapper()
sys.stdout = mystdout
sys.stderr = mystdout

# do your stuff here that needs to be printed out in a string list
for i in range(2):
    print ('i = ', i)

# you don't need to make your variable to cache the `stdout`/`stderr` as they still exist
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__

он отлично работает с python 3 и python 2.