จับ stdout จากสคริปต์?


94

สมมติว่ามีสคริปต์ที่ทำสิ่งนี้:

# module writer.py
import sys

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

ตอนนี้สมมติว่าฉันต้องการจับเอาต์พุตของwriteฟังก์ชันและเก็บไว้ในตัวแปรเพื่อประมวลผลต่อไป วิธีแก้ปัญหาที่ไร้เดียงสาคือ:

# module mymodule.py
from writer import write

out = write()
print out.upper()

แต่ไม่ได้ผล ฉันคิดวิธีแก้ปัญหาอื่นและได้ผล แต่โปรดแจ้งให้เราทราบหากมีวิธีที่ดีกว่าในการแก้ปัญหา ขอบคุณ

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

คำตอบ:


51

การตั้งค่าstdoutเป็นวิธีที่สมเหตุสมผลที่จะทำ อีกประการหนึ่งคือการเรียกใช้เป็นกระบวนการอื่น:

import subprocess

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

4
check_output จับเอาต์พุตของคำสั่งที่รันในกระบวนการย่อยโดยตรง: <br> value = subprocess.check_output (command, shell = True)
Arthur

1
รุ่นที่จัดรูปแบบ :value = subprocess.check_output(command, shell=True)
Nae

51

นี่คือเวอร์ชันตัวจัดการบริบทของโค้ดของคุณ ให้รายการของสองค่า อันดับแรกคือ stdout ที่สองคือ 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'

รักโซลูชันนี้ ฉันแก้ไขเพื่อไม่ให้ข้อมูลสูญหายโดยไม่ได้ตั้งใจจากสตรีมที่ฉันไม่ได้คาดหวังผลลัพธ์เช่นข้อผิดพลาดที่ไม่คาดคิด ในกรณีของฉัน capture () สามารถยอมรับ sys.stderr หรือ sys.stdout เป็นพารามิเตอร์เพื่อระบุเฉพาะสตรีมนั้น
Joshua Richardson

StringIO ไม่รองรับ Unicode ในรูปแบบใด ๆ ดังนั้นคุณสามารถรวมคำตอบที่นี่เพื่อสร้างการสนับสนุนที่ไม่ใช่ ASCII ข้างต้น: stackoverflow.com/a/1819009/425050
mafrosis

3
การปรับเปลี่ยนมูลค่าที่ได้รับในที่สุดก็ค่อนข้างแปลก - with capture() as out:จะทำงานแตกต่างจากwith capture() as out, err:
Eric

1
การสนับสนุน Unicode / stdout.buffer สามารถเข้าถึงได้โดยใช้โมดูล io ดูคำตอบของฉัน
JonnyJD

3
โซลูชันนี้จะหยุดทำงานหากคุณใช้subprocessและเปลี่ยนทิศทางเอาต์พุตไปยัง sys.stdout / stderr เนื่องจากStringIOไม่ใช่วัตถุไฟล์จริงและไม่มีfileno()ฟังก์ชัน
letmaik

47

สำหรับผู้เยี่ยมชมในอนาคต: Python 3.4 contextlib จัดเตรียมไว้สำหรับสิ่งนี้โดยตรง (ดูวิธีใช้Python contextlib ) ผ่านตัวredirect_stdoutจัดการบริบท:

from contextlib import redirect_stdout
import io

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

2
วิธีนี้ไม่ช่วยแก้ปัญหาเมื่อพยายามเขียนไปที่ sys.stdout.buffer (ตามที่คุณต้องทำเมื่อเขียนไบต์) StringIO ไม่มีแอตทริบิวต์บัฟเฟอร์ในขณะที่ TextIOWrapper มี ดูคำตอบได้จาก @JonnyJD
ช่างทอ

10

นี่คือมัณฑนากรคู่กับรหัสเดิมของฉัน

writer.py ยังคงเหมือนเดิม:

import sys

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

mymodule.py ได้รับการแก้ไข sligthly:

from writer import write as _write
from decorators import capture

@capture
def write():
    return _write()

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

และนี่คือมัณฑนากร:

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

หรืออาจจะใช้ฟังก์ชันที่มีอยู่แล้ว ...

from IPython.utils.capture import capture_output

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

c()

print c.stdout

9

เริ่มต้นด้วย Python 3 คุณสามารถใช้sys.stdout.buffer.write()เขียนสตริงไบต์ที่เข้ารหัส (แล้ว) เป็น stdout (ดูstdout ใน Python 3 ) เมื่อคุณทำเช่นนั้นStringIOวิธีง่ายๆจะไม่ได้ผลเพราะไม่มีsys.stdout.encodingหรือsys.stdout.bufferจะไม่สามารถใช้ได้

เริ่มต้นด้วย Python 2.6 คุณสามารถใช้TextIOBaseAPIซึ่งรวมถึงแอตทริบิวต์ที่ขาดหายไป:

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

โซลูชันนี้ใช้ได้กับ Python 2> = 2.6 และ Python 3 โปรดทราบว่าเราsys.stdout.write()ยอมรับเฉพาะสตริง Unicode และsys.stdout.buffer.write()ยอมรับเฉพาะสตริงไบต์เท่านั้น นี่อาจไม่ใช่กรณีของรหัสเก่า แต่มักเป็นกรณีของรหัสที่สร้างขึ้นเพื่อทำงานบน Python 2 และ 3 โดยไม่มีการเปลี่ยนแปลง

หากคุณต้องการสนับสนุนโค้ดที่ส่งไบต์สตริงไปยัง stdout โดยตรงโดยไม่ต้องใช้ stdout.buffer คุณสามารถใช้รูปแบบนี้:

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 แต่จะช่วยได้เมื่อใช้วิธีนี้ในการทดสอบ / เปรียบเทียบเอาต์พุตสคริปต์


4

คำถามที่นี่ (ตัวอย่างวิธีการเปลี่ยนเส้นทางเอาต์พุตไม่ใช่teeส่วน) ใช้os.dup2เพื่อเปลี่ยนเส้นทางสตรีมที่ระดับ OS นั่นเป็นสิ่งที่ดีเพราะมันจะนำไปใช้กับคำสั่งที่คุณเกิดจากโปรแกรมของคุณเช่นกัน


4

ฉันคิดว่าคุณควรดูสี่วัตถุเหล่านี้:

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

ตัวอย่าง:

from writer import write

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

UPD: ดังที่ Eric กล่าวในความคิดเห็นเราไม่ควรใช้โดยตรงดังนั้นฉันจึงคัดลอกและวาง

# 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 แต่ถ้าคุณต้องการบัฟเฟอร์ที่เก็บไว้ในไฟล์ที่เปิดอยู่และไม่มีการรองรับไฟล์คุณสามารถทำสิ่งนี้ได้

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

ใช้

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

นี่คือตัวจัดการบริบทที่ได้รับแรงบันดาลใจจากคำตอบของ @JonnyJD ที่สนับสนุนการเขียนไบต์ไปยังbufferแอตทริบิวต์โดยใช้ประโยชน์จากการอ้างอิง dunder-ioของsysเพื่อให้ง่ายขึ้น

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']))

ผลลัพธ์คือ:

stdout: foo

stderr: bar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.