จะจับเอาท์พุท stdout จากการเรียกฟังก์ชัน Python ได้อย่างไร


113

ฉันใช้ไลบรารี Python ที่ทำอะไรบางอย่างกับวัตถุ

do_something(my_object)

และเปลี่ยนแปลงมัน ในขณะที่ทำเช่นนั้นมันพิมพ์สถิติบางอย่างเพื่อ stdout และฉันต้องการที่จะเข้าใจข้อมูลนี้ วิธีแก้ปัญหาที่เหมาะสมคือการเปลี่ยนdo_something()เพื่อส่งคืนข้อมูลที่เกี่ยวข้อง

out = do_something(my_object)

แต่จะต้องใช้เวลาสักพักก่อนที่ผู้พัฒนาdo_something()จะเข้าสู่ปัญหานี้ เป็นวิธีแก้ปัญหาชั่วคราวฉันคิดถึงการแยกวิเคราะห์สิ่งที่do_something()เขียนถึง stdout

ฉันจะจับเอาท์พุท stdout ระหว่างสองจุดในโค้ดได้อย่างไรเช่น

start_capturing()
do_something(my_object)
out = end_capturing()

เหรอ?


2
อาจซ้ำกันในการจับผล dis.dis
Martijn Pieters

คำตอบของฉันในคำถามที่เชื่อมโยงอยู่ที่นี่เช่นกัน
Martijn Pieters

ฉันพยายามทำครั้งเดียวและคำตอบที่ดีที่สุดที่ฉันพบคือ stackoverflow.com/a/3113913/1330293
elyase

คำตอบที่เชื่อมโยง @elyase เป็นวิธีแก้ปัญหาที่สวยงาม
Pykler

ดูคำตอบนี้
martineau

คำตอบ:


188

ลองใช้ตัวจัดการบริบทนี้:

from io import StringIO 
import sys

class Capturing(list):
    def __enter__(self):
        self._stdout = sys.stdout
        sys.stdout = self._stringio = StringIO()
        return self
    def __exit__(self, *args):
        self.extend(self._stringio.getvalue().splitlines())
        del self._stringio    # free up some memory
        sys.stdout = self._stdout

การใช้งาน:

with Capturing() as output:
    do_something(my_object)

output ตอนนี้เป็นรายการที่มีบรรทัดที่พิมพ์โดยการเรียกใช้ฟังก์ชัน

การใช้งานขั้นสูง:

สิ่งที่อาจไม่ชัดเจนคือสามารถทำได้มากกว่าหนึ่งครั้งและผลลัพธ์ที่เชื่อมต่อกัน:

with Capturing() as output:
    print('hello world')

print('displays on screen')

with Capturing(output) as output:  # note the constructor argument
    print('hello world2')

print('done')
print('output:', output)

เอาท์พุต:

displays on screen                     
done                                   
output: ['hello world', 'hello world2']

ปรับปรุง : พวกเขาเพิ่มredirect_stdout()ไปcontextlibในหลาม 3.4 (พร้อมด้วยredirect_stderr()) ดังนั้นคุณสามารถใช้io.StringIOเพื่อให้ได้ผลลัพธ์ที่คล้ายกัน (แม้ว่าCapturingการเป็นรายการและตัวจัดการบริบทจะสะดวกกว่า)


ขอบคุณ! และขอบคุณสำหรับการเพิ่มส่วนขั้นสูง ... เดิมทีฉันใช้การกำหนดชิ้นส่วนเพื่อติดข้อความที่บันทึกไว้ในรายการจากนั้นฉันก็จมอยู่ในหัวและใช้.extend()แทนเพื่อให้สามารถใช้แบบเรียงต่อกันได้อย่างที่คุณสังเกตเห็น :-)
kindall

ป.ล. หากจะใช้ซ้ำผมขอแนะนำให้เพิ่มself._stringio.truncate(0)หลังการself.extend()โทรใน__exit__()วิธีการปล่อยหน่วยความจำบางส่วนที่_stringioสมาชิกถือไว้
martineau

25
คำตอบที่ดีขอบคุณ สำหรับ Python 3 ให้ใช้from io import StringIOแทนบรรทัดแรกในตัวจัดการบริบท
Wtower

1
เธรดนี้ปลอดภัยหรือไม่? จะเกิดอะไรขึ้นถ้าเธรด / การโทรอื่น ๆ ใช้ print () ในขณะที่ do_something ทำงาน
Derorrist

1
คำตอบนี้ใช้ไม่ได้กับเอาต์พุตจากไลบรารีที่แชร์ C โปรดดูคำตอบนี้แทน
craymichael

88

ใน python> = 3.4 contextlib มีredirect_stdoutมัณฑนากร สามารถใช้เพื่อตอบคำถามของคุณดังนี้:

import io
from contextlib import redirect_stdout

f = io.StringIO()
with redirect_stdout(f):
    do_something(my_object)
out = f.getvalue()

จากเอกสาร :

ตัวจัดการบริบทสำหรับการเปลี่ยนทิศทาง sys.stdout ชั่วคราวไปยังไฟล์อื่นหรือวัตถุที่คล้ายไฟล์

เครื่องมือนี้เพิ่มความยืดหยุ่นให้กับฟังก์ชันหรือคลาสที่มีอยู่ซึ่งเอาต์พุตเป็นแบบเดินสายไปยัง stdout

ตัวอย่างเช่นผลลัพธ์ของ help () โดยปกติจะถูกส่งไปที่ sys.stdout คุณสามารถจับเอาท์พุทนั้นเป็นสตริงโดยเปลี่ยนทิศทางเอาต์พุตไปยังอ็อบเจ็กต์ io.StringIO:

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

ในการส่งผลลัพธ์ของ help () ไปยังไฟล์บนดิสก์ให้เปลี่ยนทิศทางผลลัพธ์ไปยังไฟล์ปกติ:

 with open('help.txt', 'w') as f:
     with redirect_stdout(f):
         help(pow)

ในการส่งผลลัพธ์ของ help () ไปยัง sys.stderr:

with redirect_stdout(sys.stderr):
    help(pow)

โปรดทราบว่าผลข้างเคียงทั่วโลกบน sys.stdout หมายความว่าตัวจัดการบริบทนี้ไม่เหมาะสำหรับใช้ในไลบรารีโค้ดและแอปพลิเคชันเธรดส่วนใหญ่ นอกจากนี้ยังไม่มีผลกับเอาต์พุตของกระบวนการย่อย อย่างไรก็ตามยังคงเป็นแนวทางที่มีประโยชน์สำหรับสคริปต์ยูทิลิตี้จำนวนมาก

ตัวจัดการบริบทนี้ reentrant


f = io.StringIO() with redirect_stdout(f): logger = getLogger('test_logger') logger.debug('Test debug message') out = f.getvalue() self.assertEqual(out, 'DEBUG:test_logger:Test debug message')เมื่อพยายาม มันทำให้ฉันมีข้อผิดพลาด:AssertionError: '' != 'Test debug message'
Eziz Durdyyev

ซึ่งหมายความว่าฉันทำอะไรผิดพลาดหรือไม่สามารถจับบันทึก stdout ได้
Eziz Durdyyev

@EzizDurdyyev logger.debugไม่ได้เขียนถึง stdout โดยค่าเริ่มต้น หากคุณแทนที่บันทึกการโทรของprint()คุณคุณจะเห็นข้อความ
ForeverWintr

ใช่ฉันรู้ แต่ฉันจะทำให้มันเขียนไป stdout stream_handler = logging.StreamHandler(sys.stdout)ชอบโดย: และเพิ่มตัวจัดการนั้นในคนตัดไม้ของฉัน ดังนั้นควรเขียนถึง stdout และredirect_stdoutควรจับมันใช่ไหม?
Eziz Durdyyev

ฉันสงสัยว่าปัญหาเกิดจากวิธีที่คุณกำหนดค่าคนตัดไม้ของคุณ ฉันจะตรวจสอบว่ามันพิมพ์ไปยัง stdout โดยไม่มี redirect_stdout ถ้าเป็นเช่นนั้นอาจไม่ได้ล้างบัฟเฟอร์จนกว่าตัวจัดการบริบทจะออก
ForeverWintr

0

นี่คือโซลูชัน async โดยใช้ file ไปป์

import threading
import sys
import os

class Capturing():
    def __init__(self):
        self._stdout = None
        self._stderr = None
        self._r = None
        self._w = None
        self._thread = None
        self._on_readline_cb = None

    def _handler(self):
        while not self._w.closed:
            try:
                while True:
                    line = self._r.readline()
                    if len(line) == 0: break
                    if self._on_readline_cb: self._on_readline_cb(line)
            except:
                break

    def print(self, s, end=""):
        print(s, file=self._stdout, end=end)

    def on_readline(self, callback):
        self._on_readline_cb = callback

    def start(self):
        self._stdout = sys.stdout
        self._stderr = sys.stderr
        r, w = os.pipe()
        r, w = os.fdopen(r, 'r'), os.fdopen(w, 'w', 1)
        self._r = r
        self._w = w
        sys.stdout = self._w
        sys.stderr = self._w
        self._thread = threading.Thread(target=self._handler)
        self._thread.start()

    def stop(self):
        self._w.close()
        if self._thread: self._thread.join()
        self._r.close()
        sys.stdout = self._stdout
        sys.stderr = self._stderr

ตัวอย่างการใช้งาน:

from Capturing import *
import time

capturing = Capturing()

def on_read(line):
    # do something with the line
    capturing.print("got line: "+line)

capturing.on_readline(on_read)
capturing.start()
print("hello 1")
time.sleep(1)
print("hello 2")
time.sleep(1)
print("hello 3")
capturing.stop()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.