Posso redirecionar o stdout em python para algum tipo de buffer de string?


138

Estou usando python ftplibpara escrever um pequeno cliente FTP, mas algumas das funções do pacote não retornam saída de string, mas imprimem em stdout. Quero redirecionar stdoutpara um objeto do qual poderei ler a saída.

Eu sei que stdoutpode ser redirecionado para qualquer arquivo regular com:

stdout = open("file", "a")

Mas eu prefiro um método que não use a unidade local.

Estou procurando algo como o BufferedReaderem Java que pode ser usado para quebrar um buffer em um fluxo.


Eu não acho que stdout = open("file", "a")por si só irá redirecionar qualquer coisa.
Alexey #

Respostas:


209
from cStringIO import StringIO # Python3 use: from io import StringIO
import sys

old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()

# blah blah lots of code ...

sys.stdout = old_stdout

# examine mystdout.getvalue()

52
+1, você não precisa manter uma referência ao stdoutobjeto original , pois ele está sempre disponível em sys.__stdout__. Consulte docs.python.org/library/sys.html#sys.__stdout__ .
Ayman Hourieh 02/08/09

92
Bem, esse é um debate interessante. O stdout original absoluto está disponível, mas ao ser substituído dessa maneira, é melhor usar um salvamento explícito como eu fiz, pois outra pessoa poderia ter substituído o stdout e, se você usar o stdout , substituirá sua substituição.
Ned Batchelder

5
esta operação em um thread alteraria o comportamento de outros threads? Quero dizer, é seguro?
Anuvrat Parashar

6
Eu recomendo reatribuir o stdout antigo em um finally:bloco, para que também seja reatribuído se uma exceção surgir no meio. try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn

20
Se você quiser usar isso no Python 3, substitua cStringIO por io.
Anthony Labarre 29/11


35

Apenas para adicionar à resposta de Ned acima: você pode usar isso para redirecionar a saída para qualquer objeto que implemente um método write (str) .

Isso pode ser usado com bom efeito para "capturar" a saída stdout em um aplicativo GUI.

Aqui está um exemplo bobo no PyQt:

import sys
from PyQt4 import QtGui

class OutputWindow(QtGui.QPlainTextEdit):
    def write(self, txt):
        self.appendPlainText(str(txt))

app = QtGui.QApplication(sys.argv)
out = OutputWindow()
sys.stdout=out
out.show()
print "hello world !"

5
Funciona para mim com python 2.6 e PyQT4. Parece estranho baixar o código de trabalho quando você não sabe por que ele não funciona!
Nicolas Lefebvre

9
não esqueça de adicionar flush () também!
Will

6

A partir do Python 2.6, você pode usar qualquer coisa que implemente a TextIOBaseAPI do módulo io como um substituto. Essa solução também permite que você use sys.stdout.buffer.write()no Python 3 para escrever (já) cadeias de bytes codificadas no stdout (consulte stdout no Python 3 ). Usar StringIOnão funcionaria então, porque sys.stdout.encodingnem sys.stdout.bufferestaria disponível.

Uma solução usando o TextIOWrapper:

import sys
from io import TextIOWrapper, BytesIO

# setup the environment
old_stdout = sys.stdout
sys.stdout = TextIOWrapper(BytesIO(), sys.stdout.encoding)

# do something that writes to stdout or stdout.buffer

# 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

Esta solução funciona para Python 2> = 2.6 e Python 3.

Observe que nosso novo sys.stdout.write()aceita apenas cadeias unicode e sys.stdout.buffer.write()somente aceita cadeias de bytes. Pode não ser o caso do código antigo, mas geralmente é o caso do código criado para ser executado no Python 2 e 3 sem alterações, o que novamente é utilizado sys.stdout.buffer.

Você pode criar uma pequena variação que aceita cadeias de caracteres unicode e bytes para write():

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)

Você não precisa definir a codificação do buffer como sys.stdout.encoding, mas isso ajuda ao usar esse método para testar / comparar a saída de script.


Essa resposta me ajudou ao configurar o parâmetro stdout de um objeto Environment para uso com o core.py do Httpie.
fragorl

6

Este método restaura o sys.stdout mesmo se houver uma exceção. Ele também obtém qualquer saída antes da exceção.

import io
import sys

real_stdout = sys.stdout
fake_stdout = io.BytesIO()   # or perhaps io.StringIO()
try:
    sys.stdout = fake_stdout
    # do what you have to do to create some output
finally:
    sys.stdout = real_stdout
    output_string = fake_stdout.getvalue()
    fake_stdout.close()
    # do what you want with the output_string

Testado em Python 2.7.10 usando io.BytesIO()

Testado em Python 3.6.4 usando io.StringIO()


Bob, adicionado a um caso, se você sentir que alguma coisa da experimentação de código modificado / estendido pode ser interessante em qualquer sentido, caso contrário, fique à vontade para excluí-lo

Ad informandum ... algumas observações de experimentação prolongada durante a descoberta de algumas mecânicas viáveis ​​para "capturar" saídas, direcionadas numexpr.print_versions()diretamente para o <stdout>(mediante a necessidade de limpar a GUI e coletar detalhes no relatório de depuração)

# THIS WORKS AS HELL: as Bob Stein proposed years ago:
#  py2 SURPRISEDaBIT:
#
import io
import sys
#
real_stdout = sys.stdout                        #           PUSH <stdout> ( store to REAL_ )
fake_stdout = io.BytesIO()                      #           .DEF FAKE_
try:                                            # FUSED .TRY:
    sys.stdout.flush()                          #           .flush() before
    sys.stdout = fake_stdout                    #           .SET <stdout> to use FAKE_
    # ----------------------------------------- #           +    do what you gotta do to create some output
    print 123456789                             #           + 
    import  numexpr                             #           + 
    QuantFX.numexpr.__version__                 #           + [3] via fake_stdout re-assignment, as was bufferred + "late" deferred .get_value()-read into print, to finally reach -> real_stdout
    QuantFX.numexpr.print_versions()            #           + [4] via fake_stdout re-assignment, as was bufferred + "late" deferred .get_value()-read into print, to finally reach -> real_stdout
    _ = os.system( 'echo os.system() redir-ed' )#           + [1] via real_stdout                                 + "late" deferred .get_value()-read into print, to finally reach -> real_stdout, if not ( _ = )-caught from RET-d "byteswritten" / avoided from being injected int fake_stdout
    _ = os.write(  sys.stderr.fileno(),         #           + [2] via      stderr                                 + "late" deferred .get_value()-read into print, to finally reach -> real_stdout, if not ( _ = )-caught from RET-d "byteswritten" / avoided from being injected int fake_stdout
                       b'os.write()  redir-ed' )#  *OTHERWISE, if via fake_stdout, EXC <_io.BytesIO object at 0x02C0BB10> Traceback (most recent call last):
    # ----------------------------------------- #           ?                              io.UnsupportedOperation: fileno
    #'''                                                    ? YET:        <_io.BytesIO object at 0x02C0BB10> has a .fileno() method listed
    #>>> 'fileno' in dir( sys.stdout )       -> True        ? HAS IT ADVERTISED,
    #>>> pass;            sys.stdout.fileno  -> <built-in method fileno of _io.BytesIO object at 0x02C0BB10>
    #>>> pass;            sys.stdout.fileno()-> Traceback (most recent call last):
    #                                             File "<stdin>", line 1, in <module>
    #                                           io.UnsupportedOperation: fileno
    #                                                       ? BUT REFUSES TO USE IT
    #'''
finally:                                        # == FINALLY:
    sys.stdout.flush()                          #           .flush() before ret'd back REAL_
    sys.stdout = real_stdout                    #           .SET <stdout> to use POP'd REAL_
    sys.stdout.flush()                          #           .flush() after  ret'd back REAL_
    out_string = fake_stdout.getvalue()         #           .GET string           from FAKE_
    fake_stdout.close()                         #                <FD>.close()
    # +++++++++++++++++++++++++++++++++++++     # do what you want with the out_string
    #
    print "\n{0:}\n{1:}{0:}".format( 60 * "/\\",# "LATE" deferred print the out_string at the very end reached -> real_stdout
                                     out_string #                   
                                     )
'''
PASS'd:::::
...
os.system() redir-ed
os.write()  redir-ed
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
123456789
'2.5'
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Numexpr version:   2.5
NumPy version:     1.10.4
Python version:    2.7.13 |Anaconda 4.0.0 (32-bit)| (default, May 11 2017, 14:07:41) [MSC v.1500 32 bit (Intel)]
AMD/Intel CPU?     True
VML available?     True
VML/MKL version:   Intel(R) Math Kernel Library Version 11.3.1 Product Build 20151021 for 32-bit applications
Number of threads used by default: 4 (out of 4 detected cores)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
>>>

EXC'd :::::
...
os.system() redir-ed
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
123456789
'2.5'
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Numexpr version:   2.5
NumPy version:     1.10.4
Python version:    2.7.13 |Anaconda 4.0.0 (32-bit)| (default, May 11 2017, 14:07:41) [MSC v.1500 32 bit (Intel)]
AMD/Intel CPU?     True
VML available?     True
VML/MKL version:   Intel(R) Math Kernel Library Version 11.3.1 Product Build 20151021 for 32-bit applications
Number of threads used by default: 4 (out of 4 detected cores)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

Traceback (most recent call last):
  File "<stdin>", line 9, in <module>
io.UnsupportedOperation: fileno
'''

6

Um gerenciador de contexto para python3:

import sys
from io import StringIO


class RedirectedStdout:
    def __init__(self):
        self._stdout = None
        self._string_io = None

    def __enter__(self):
        self._stdout = sys.stdout
        sys.stdout = self._string_io = StringIO()
        return self

    def __exit__(self, type, value, traceback):
        sys.stdout = self._stdout

    def __str__(self):
        return self._string_io.getvalue()

use assim:

>>> with RedirectedStdout() as out:
>>>     print('asdf')
>>>     s = str(out)
>>>     print('bsdf')
>>> print(s, out)
'asdf\n' 'asdf\nbsdf\n'

4

No Python3.6, os módulos StringIOe cStringIOse foram, você deve usá- io.StringIOlo. Portanto, faça isso como a primeira resposta:

import sys
from io import StringIO

old_stdout = sys.stdout
old_stderr = sys.stderr
my_stdout = sys.stdout = StringIO()
my_stderr = sys.stderr = StringIO()

# blah blah lots of code ...

sys.stdout = self.old_stdout
sys.stderr = self.old_stderr

// if you want to see the value of redirect output, be sure the std output is turn back
print(my_stdout.getvalue())
print(my_stderr.getvalue())

my_stdout.close()
my_stderr.close()

1
Você pode melhorar a qualidade da sua resposta explicando como o código acima funciona e como isso é uma melhoria na situação do interlocutor.
toonice


1

Aqui está outra opinião sobre isso. contextlib.redirect_stdoutcom io.StringIO()o documentado é ótimo, mas ainda é um pouco detalhado para o uso diário. Veja como torná-lo uma linha única subclassificando contextlib.redirect_stdout:

import sys
import io
from contextlib import redirect_stdout

class capture(redirect_stdout):

    def __init__(self):
        self.f = io.StringIO()
        self._new_target = self.f
        self._old_targets = []  # verbatim from parent class

    def __enter__(self):
        self._old_targets.append(getattr(sys, self._stream))  # verbatim from parent class
        setattr(sys, self._stream, self._new_target)  # verbatim from parent class
        return self  # instead of self._new_target in the parent class

    def __repr__(self):
        return self.f.getvalue()  

Como __enter__ retorna self, você tem o objeto do gerenciador de contexto disponível após a saída do bloco with. Além disso, graças ao método __repr__, a representação em cadeia do objeto gerenciador de contexto é, de fato, stdout. Então agora você tem,

with capture() as message:
    print('Hello World!')
print(str(message)=='Hello World!\n')  # returns True
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.