Python içindeki stdout'u bir çeşit dize arabelleğine yönlendirebilir miyim?


138

ftplibKüçük bir FTP istemcisi yazmak için python's kullanıyorum , ancak paketteki bazı işlevler dize çıktısı döndürmez, ancak yazdırır stdout. stdoutÇıktıyı okuyabileceğim bir nesneye yönlendirmek istiyorum .

Ben stdoutile herhangi bir normal dosyaya yönlendirilebilir biliyorum :

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

Ancak yerel sürücüyü kullanmayan bir yöntemi tercih ediyorum.

BufferedReaderJava gibi bir şey bir akış içine bir arabellek sarmak için kullanılabilecek bir şey arıyorum .


stdout = open("file", "a")Kendi başına bir şey yönlendireceğini düşünmüyorum .
Alexey

Yanıtlar:


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, orijinal stdoutnesne için her zaman kullanılabilir olduğu için bir referans tutmanız gerekmez sys.__stdout__. Bkz. Docs.python.org/library/sys.html#sys.__stdout__ .
Ayman Hourieh

92
Bu ilginç bir tartışma. Mutlak orijinal stdout mevcuttur, ancak böyle değiştirirken, yaptığım gibi açık bir kayıt kullanmak daha iyidir, çünkü başka biri stdout'un yerini alabilir ve stdout kullanırsanız , değiştirmelerini hızlandırabilirsiniz.
Ned Batchelder

5
bir iş parçacığındaki bu işlem diğer iş parçacıklarının davranışını değiştirir mi? Yani threadsafe mi?
Anuvrat Parashar

6
Eski stdout'u bir finally:blokta yeniden atamanızı şiddetle tavsiye ederim , bu yüzden arada bir istisna yükselirse yeniden atanır. try: bkp = sys.stdout ... ... finally: sys.stdout = bkp
Matthias Kuhn

20
Bunu Python 3'te kullanmak istiyorsanız, cStringIO'yu io ile değiştirin.
Anthony Labarre


35

Sadece Ned'in cevabına eklemek için: çıktıyı bir write (str) yöntemi uygulayan herhangi bir nesneye yönlendirmek için bunu kullanabilirsiniz .

Bu, bir GUI uygulamasında stdout çıktısını "yakalamak" için iyi bir etki sağlamak üzere kullanılabilir.

İşte PyQt'ta aptalca bir örnek:

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
Python 2.6 ve PyQT4 ile benim için çalışıyor. Neden işe yaramadığını anlayamadığınızda, çalışma kodunu aşağıya oylamak garip görünüyor!
Nicolas Lefebvre

9
flush () eklemeyi de unutmayın!
Will

6

Python 2.6 ile başlayarak , io modülünden TextIOBaseAPI'yi uygulayan her şeyi yedek olarak kullanabilirsiniz. Bu çözüm ayrıca sys.stdout.buffer.write()Python 3'te kodlanmış bayt dizelerini stdout'a yazmak için (zaten) Python 3'te kullanmanızı sağlar (bkz . Python 3'teki stdout ). Kullanmak StringIOo zaman işe yaramaz, çünkü ne sys.stdout.encodingne sys.stdout.bufferde kullanılabilir.

TextIOWrapper kullanan bir çözüm:

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

Bu çözüm Python 2> = 2.6 ve Python 3 için çalışır.

Yeni sys.stdout.write()sürümümüzün yalnızca unicode dizeleri kabul ettiğini ve sys.stdout.buffer.write()yalnızca bayt dizelerini kabul ettiğini lütfen unutmayın . Bu, eski kod için geçerli olmayabilir, ancak Python 2 ve 3'te değişiklik yapılmadan çalışmak için oluşturulmuş kod için genellikle tekrar kullanılır sys.stdout.buffer.

Unicode ve bayt dizelerini kabul eden küçük bir varyasyon oluşturabilirsiniz 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)

Sys.stdout.encoding arabelleğinin kodlamasını ayarlamak zorunda değilsiniz, ancak bu, komut dosyası çıktısını test etmek / karşılaştırmak için bu yöntemi kullanırken yardımcı olur.


Bu cevap Httpie'nin core.py ile kullanmak için bir Environment nesnesinin stdout parametresini ayarlarken bana yardımcı oldu.
fragorl

6

Bu yöntem, bir istisna olsa bile sys.stdout'u geri yükler. İstisnadan önce de herhangi bir çıktı alır.

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

Python 2.7.10'da test edilmiştir. io.BytesIO()

Python 3.6.4 ile test edilmiştir. io.StringIO()


Bob, değiştirilmiş / genişletilmiş kod denemesinden herhangi bir şeyin ilginç olabileceğini düşünüyorsanız , aksi takdirde silmekten çekinmeyin

Reklam bilgileri ... numexpr.print_versions()doğrudan geçerli olan <stdout>(GUI'yi temizleme ihtiyacı ve hata ayıklama raporuna ayrıntılar toplama) tarafından yönlendirilen çıktıları almak için bazı uygulanabilir mekanikler bulma sırasında genişletilmiş deneylerden birkaç açıklama

# 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

Python3 için bir içerik yöneticisi:

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()

şöyle kullanın:

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

4

Python3.6'da StringIOve cStringIOmodülleri gitti, bunun io.StringIOyerine kullanmalısınız.Yani bunu ilk cevap gibi yapmalısınız:

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
Yukarıdaki kodun nasıl çalıştığını ve bunun Sorunun durumu üzerinde nasıl bir gelişme olduğunu açıklayarak Yanıtınızın kalitesini artırabilirsiniz.
toonice


1

İşte buna başka bir örnek. contextlib.redirect_stdoutile belgelenmişio.StringIO() gibi harika, ama yine de her gün kullanım için biraz ayrıntılı. Alt sınıflandırma ile nasıl tek katmanlı hale getirileceği aşağıda açıklanmıştır :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()  

__Enter__ kendini döndürdüğünden, with block çıkışlarından sonra bağlam yöneticisi nesnesine sahip olursunuz. Ayrıca, __repr__ yöntemi sayesinde, bağlam yöneticisi nesnesinin dize temsili aslında stdout'tur. Şimdi sahipsin,

with capture() as message:
    print('Hello World!')
print(str(message)=='Hello World!\n')  # returns True
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.