Puis-je rediriger le stdout en python dans une sorte de tampon de chaîne?

138

J'utilise python ftplibpour écrire un petit client FTP, mais certaines des fonctions du package ne retournent pas de sortie de chaîne, mais impriment sur stdout. Je veux rediriger stdoutvers un objet dont je pourrai lire la sortie.

Je sais que je stdoutpeux être redirigé vers n'importe quel fichier régulier avec:

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

Mais je préfère une méthode qui n'utilise pas le lecteur local.

Je recherche quelque chose comme le BufferedReaderen Java qui peut être utilisé pour envelopper un tampon dans un flux.

Avihu Turzion
la source
Je ne pense pas à stdout = open("file", "a")lui seul redirigera quoi que ce soit.
Alexey

Réponses:

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()
Ned Batchelder
la source
52
+1, vous n'avez pas besoin de conserver une référence à l' stdoutobjet d' origine , car il est toujours disponible sur sys.__stdout__. Voir docs.python.org/library/sys.html#sys.__stdout__ .
Ayman Hourieh
92
Eh bien, c'est un débat intéressant. Le stdout d'origine absolu est disponible, mais lors du remplacement comme celui-ci, il est préférable d'utiliser une sauvegarde explicite comme je l'ai fait, car quelqu'un d'autre aurait pu remplacer stdout et si vous utilisez stdout , vous réduirez leur remplacement.
Ned Batchelder
5
cette opération dans un thread modifierait-elle le comportement des autres threads? Je veux dire, est-ce threadsafe?
Anuvrat Parashar
6
Je recommande fortement de réaffecter l'ancien stdout dans un finally:bloc, il est donc également réaffecté si une exception est levée entre les deux. try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn
20
Si vous souhaitez l'utiliser dans Python 3, remplacez cStringIO par io.
Anthony Labarre
80

Il existe une fonction contextlib.redirect_stdout () dans Python 3.4:

import io
from contextlib import redirect_stdout

with io.StringIO() as buf, redirect_stdout(buf):
    print('redirected')
    output = buf.getvalue()

Voici un exemple de code qui montre comment l'implémenter sur les anciennes versions de Python .

jfs
la source
3
Il y a aussi redirect_stderrle dernier Python!
CMCDragonkai
Je pense qu'il n'est pas nécessaire d'ajouter un bloc try / finally pour cette solution.
snr
35

Juste pour ajouter à la réponse de Ned ci-dessus: vous pouvez l'utiliser pour rediriger la sortie vers n'importe quel objet qui implémente une méthode write (str) .

Cela peut être utilisé à bon escient pour "capturer" la sortie stdout dans une application GUI.

Voici un exemple idiot dans 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 !"
Nicolas Lefebvre
la source
5
Fonctionne pour moi avec python 2.6 et PyQT4. Cela semble étrange de rejeter le code de travail lorsque vous ne pouvez pas dire pourquoi cela ne fonctionne pas!
Nicolas Lefebvre
9
n'oubliez pas d'ajouter flush () aussi!
Sera
6

À partir de Python 2.6, vous pouvez utiliser tout ce qui implémente l' TextIOBaseAPI du module io en remplacement. Cette solution vous permet également d'utiliser sys.stdout.buffer.write()en Python 3 pour écrire des chaînes d'octets (déjà) encodées dans stdout (voir stdout dans Python 3 ). Utiliser StringIOne fonctionnerait pas alors, car ni sys.stdout.encodingnisys.stdout.buffer ne seraient disponibles.

Une solution utilisant 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

Cette solution fonctionne pour Python 2> = 2.6 et Python 3.

Veuillez noter que notre nouveau sys.stdout.write()n'accepte que les chaînes Unicode et sys.stdout.buffer.write()n'accepte que les chaînes d'octets. Ce n'est peut-être pas le cas pour l'ancien code, mais c'est souvent le cas pour le code qui est construit pour s'exécuter sur Python 2 et 3 sans modifications, qui à nouveau utilise souventsys.stdout.buffer .

Vous pouvez créer une légère variante qui accepte les chaînes unicode et d'octets pour 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)

Vous n'êtes pas obligé de définir l'encodage du tampon sys.stdout.encoding, mais cela aide lorsque vous utilisez cette méthode pour tester / comparer la sortie de script.

JonnyJD
la source
Cette réponse m'a aidé lors de la configuration du paramètre stdout d'un objet Environnement à utiliser avec core.py de Httpie.
fragorl
6

Cette méthode restaure sys.stdout même s'il y a une exception. Il obtient également n'importe quelle sortie avant l'exception.

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

Testé en Python 2.7.10 avec io.BytesIO()

Testé en Python 3.6.4 avec io.StringIO()


Bob, ajouté pour un cas si vous pensez que quelque chose de l'expérimentation de code modifié / étendu pourrait devenir intéressant dans n'importe quel sens, sinon n'hésitez pas à le supprimer

Ad informandum ... quelques remarques d'expérimentation étendue lors de la recherche de mécanismes viables pour "saisir" les sorties, numexpr.print_versions()directement dirigées vers le <stdout>(sur un besoin de nettoyer l'interface graphique et de collecter les détails dans le rapport de débogage)

# 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
'''
Bob Stein
la source
6

Un gestionnaire de contexte pour 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()

utiliser comme ceci:

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

Dans Python3.6, les modules StringIOet cStringIOont disparu, vous devriez les utiliser à la io.StringIOplace, vous devriez donc faire ceci comme la première réponse:

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()
haofly
la source
1
Vous pouvez améliorer la qualité de votre réponse en expliquant comment fonctionne le code ci-dessus et en quoi il s'agit d'une amélioration par rapport à la situation de l'interlocuteur.
toonice
1

Voici une autre prise à ce sujet. contextlib.redirect_stdoutavec io.StringIO()comme documenté c'est super, mais c'est quand même un peu bavard pour une utilisation quotidienne. Voici comment en faire un one-liner en sous-classant 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()  

Puisque __enter__ retourne self, vous avez l'objet de gestionnaire de contexte disponible après la sortie du bloc with. De plus, grâce à la méthode __repr__, la représentation sous forme de chaîne de l'objet gestionnaire de contexte est en fait stdout. Alors maintenant tu as,

with capture() as message:
    print('Hello World!')
print(str(message)=='Hello World!\n')  # returns True
pandichef
la source