Bir komut dosyasından stdout yakalamak mı?


94

bunun gibi bir şey yapan bir komut dosyası olduğunu varsayalım:

# module writer.py
import sys

def write():
    sys.stdout.write("foobar")

Şimdi, writefonksiyonun çıktısını yakalamak ve daha sonraki işlemler için bir değişkende saklamak istediğimi varsayalım . Saf çözüm şuydu:

# module mymodule.py
from writer import write

out = write()
print out.upper()

Ama bu işe yaramıyor. Başka bir çözüm buldum ve işe yarıyor, ancak sorunu çözmenin daha iyi bir yolu olup olmadığını lütfen bana bildirin. Teşekkürler

import sys
from cStringIO import StringIO

# setup the environment
backup = sys.stdout

# ####
sys.stdout = StringIO()     # capture output
write()
out = sys.stdout.getvalue() # release output
# ####

sys.stdout.close()  # close the stream 
sys.stdout = backup # restore original stdout

print out.upper()   # post processing

Yanıtlar:


51

Ayarlama stdout, bunu yapmanın makul bir yoludur. Bir diğeri, onu başka bir işlem olarak çalıştırmaktır:

import subprocess

proc = subprocess.Popen(["python", "-c", "import writer; writer.write()"], stdout=subprocess.PIPE)
out = proc.communicate()[0]
print out.upper()

4
check_output, bir alt işlemde çalıştırılan bir komutun çıktısını doğrudan yakalar: <br> value = subprocess.check_output (command, shell = True)
Arthur

1
Biçimlendirilmiş sürüm :value = subprocess.check_output(command, shell=True)
Nae

51

İşte kodunuzun bağlam yöneticisi sürümü. İki değerden oluşan bir liste verir; birincisi standart çıktı, ikincisi stderr.

import contextlib
@contextlib.contextmanager
def capture():
    import sys
    from cStringIO import StringIO
    oldout,olderr = sys.stdout, sys.stderr
    try:
        out=[StringIO(), StringIO()]
        sys.stdout,sys.stderr = out
        yield out
    finally:
        sys.stdout,sys.stderr = oldout, olderr
        out[0] = out[0].getvalue()
        out[1] = out[1].getvalue()

with capture() as out:
    print 'hi'

Bu çözümü seviyorum. Çıktı beklemediğim bir akıştan kazara bir şeyler kaybetmemek için değişiklik yaptım, örneğin beklenmeyen hatalar. Benim durumumda, capture () sys.stderr veya sys.stdout'u bir parametre olarak kabul edebilir ve yalnızca bu akışı yakalamayı belirtir.
Joshua Richardson

StringIO, unicode'u hiçbir şekilde desteklemez, bu nedenle yukarıdaki ASCII olmayan karakterleri desteklemek için cevabı buraya entegre edebilirsiniz: stackoverflow.com/a/1819009/425050
mafrosis

3
Verilen bir değeri nihayetinde değiştirmek gerçekten biraz garip - with capture() as out:farklı davranacakwith capture() as out, err:
Eric

1
Unicode / stdout.buffer desteğine io modülü kullanılarak ulaşılabilir. Cevabımı gör .
JonnyJD

3
subprocessÇıktıyı sys.stdout / stderr'e yeniden yönlendirirseniz bu çözüm bozulur . Bunun nedeni StringIOgerçek bir dosya nesnesi olmaması ve fileno()işlevi kaçırmasıdır .
letmaik

47

Gelecek ziyaretçiler için: Python 3.4 contextlib , bağlam yöneticisi aracılığıyla bunu doğrudan sağlar ( Python contextlib yardımına bakın ) redirect_stdout:

from contextlib import redirect_stdout
import io

f = io.StringIO()
with redirect_stdout(f):
    help(pow)
s = f.getvalue()

2
Bu, sys.stdout.buffer'a yazmaya çalışırken sorunu çözmez (bayt yazarken yapmanız gerektiği gibi). StringIO, buffer niteliğine sahip değil, TextIOWrapper ise. @JonnyJD'den gelen cevaba bakın.
weaver

10

Bu benim orijinal kodumun dekoratör karşılığıdır.

writer.py aynı kalmak:

import sys

def write():
    sys.stdout.write("foobar")

mymodule.py azıcık değiştirilir:

from writer import write as _write
from decorators import capture

@capture
def write():
    return _write()

out = write()
# out post processing...

Ve işte dekoratör:

def capture(f):
    """
    Decorator to capture standard output
    """
    def captured(*args, **kwargs):
        import sys
        from cStringIO import StringIO

        # setup the environment
        backup = sys.stdout

        try:
            sys.stdout = StringIO()     # capture output
            f(*args, **kwargs)
            out = sys.stdout.getvalue() # release output
        finally:
            sys.stdout.close()  # close the stream 
            sys.stdout = backup # restore original stdout

        return out # captured output wrapped in a string

    return captured

10

Ya da belki zaten var olan işlevselliği kullanın ...

from IPython.utils.capture import capture_output

with capture_output() as c:
    print('some output')

c()

print c.stdout

9

Python 3 ile başlayarak, standart çıktıya sys.stdout.buffer.write()(zaten) kodlanmış bayt dizelerini yazmak için de kullanabilirsiniz ( Python 3'teki stdout'a bakın ). Bunu yaptığınızda, basit StringIOyaklaşım işe yaramaz çünkü ne mevcut ne sys.stdout.encodingde sys.stdout.bufferolmayacak.

Python 2.6'dan başlayarak , eksik öznitelikleri içeren TextIOBaseAPI'yi kullanabilirsiniz :

import sys
from io import TextIOWrapper, BytesIO

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

# do some writing (indirectly)
write("blub")

# 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

# do stuff with the output
print(out.upper())

Bu çözüm Python 2> = 2.6 ve Python 3 için çalışır. Lütfen bizim sys.stdout.write()sadece unicode dizgelerini ve sys.stdout.buffer.write()yalnızca bayt dizelerini kabul ettiğimizi unutmayın . Bu, eski kod için geçerli olmayabilir, ancak genellikle Python 2 ve 3 üzerinde değişiklik yapılmadan çalışacak şekilde oluşturulmuş kod için geçerlidir.

Stdout.buffer'ı kullanmadan doğrudan stdout'a bayt dizeleri gönderen kodu desteklemeniz gerekiyorsa, bu varyasyonu kullanabilirsiniz:

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)

Arabelleğin kodlamasını sys.stdout.encoding olarak ayarlamanız gerekmez, ancak bu, komut dosyası çıktısını test etmek / karşılaştırmak için bu yöntemi kullanırken yardımcı olur.


4

Sorusu burada (çıktısını yönlendirmek için nasıl örnek değil, teeparçası) kullanan os.dup2OS seviyesinde akışı yönlendirmek için. Bu güzel çünkü programınızdan oluşturduğunuz komutlar için de geçerli olacak.


4

Bence şu dört nesneye bakmalısınız:

from test.test_support import captured_stdout, captured_output, \
    captured_stderr, captured_stdin

Misal:

from writer import write

with captured_stdout() as stdout:
    write()
print stdout.getvalue().upper()

UPD: Eric'in bir yorumda söylediği gibi, doğrudan kullanmamalı, ben de kopyalayıp yapıştırdım.

# Code from test.test_support:
import contextlib
import sys

@contextlib.contextmanager
def captured_output(stream_name):
    """Return a context manager used by captured_stdout and captured_stdin
    that temporarily replaces the sys stream *stream_name* with a StringIO."""
    import StringIO
    orig_stdout = getattr(sys, stream_name)
    setattr(sys, stream_name, StringIO.StringIO())
    try:
        yield getattr(sys, stream_name)
    finally:
        setattr(sys, stream_name, orig_stdout)

def captured_stdout():
    """Capture the output of sys.stdout:

       with captured_stdout() as s:
           print "hello"
       self.assertEqual(s.getvalue(), "hello")
    """
    return captured_output("stdout")

def captured_stderr():
    return captured_output("stderr")

def captured_stdin():
    return captured_output("stdin")

4

Contextmanager çözümünü beğendim ancak açık dosya ve dosya desteği ile depolanan tampona ihtiyacınız varsa, bunun gibi bir şey yapabilirsiniz.

import six
from six.moves import StringIO


class FileWriteStore(object):
    def __init__(self, file_):
        self.__file__ = file_
        self.__buff__ = StringIO()

    def __getattribute__(self, name):
        if name in {
            "write", "writelines", "get_file_value", "__file__",
                "__buff__"}:
            return super(FileWriteStore, self).__getattribute__(name)
        return self.__file__.__getattribute__(name)

    def write(self, text):
        if isinstance(text, six.string_types):
            try:
                self.__buff__.write(text)
            except:
                pass
        self.__file__.write(text)

    def writelines(self, lines):
        try:
            self.__buff__.writelines(lines)
        except:
            pass
        self.__file__.writelines(lines)

    def get_file_value(self):
        return self.__buff__.getvalue()

kullanım

import sys
sys.stdout = FileWriteStore(sys.stdout)
print "test"
buffer = sys.stdout.get_file_value()
# you don't want to print the buffer while still storing
# else it will double in size every print
sys.stdout = sys.stdout.__file__
print buffer

2

İşte JonnyJD en @ gelen bir bağlam yöneticisi alma ilham kaynağı cevap yazma destekleyen bayt bufferniteliklerin de yararlanarak dayanacak sys en dunder-io Referanzprojesi daha da sadeleştirilmesi için.

import io
import sys
import contextlib


@contextlib.contextmanager
def capture_output():
    output = {}
    try:
        # Redirect
        sys.stdout = io.TextIOWrapper(io.BytesIO(), sys.stdout.encoding)
        sys.stderr = io.TextIOWrapper(io.BytesIO(), sys.stderr.encoding)
        yield output
    finally:
        # Read
        sys.stdout.seek(0)
        sys.stderr.seek(0)
        output['stdout'] = sys.stdout.read()
        output['stderr'] = sys.stderr.read()
        sys.stdout.close()
        sys.stderr.close()

        # Restore
        sys.stdout = sys.__stdout__
        sys.stderr = sys.__stderr__


with capture_output() as output:
    print('foo')
    sys.stderr.buffer.write(b'bar')

print('stdout: {stdout}'.format(stdout=output['stdout']))
print('stderr: {stderr}'.format(stderr=output['stderr']))

Çıktı:

stdout: foo

stderr: bar
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.