python – 实时拦截子进程输出的问题

我花了大约6个小时来堆栈溢出,重写我的python代码并尝试让它工作.它只是没有.不管我做什么.

目标:
使子流程的输出实时显示在tkinter文本框中.

问题:
我无法弄清楚如何让Popen实时工作.它似乎一直挂起,直到该过程完成. (单独运行,该过程完全按预期工作,所以只有这个有错误)

相关代码:

import os
import tkinter
import tkinter.ttk as tk
import subprocess

class Application (tk.Frame):
    process = 0
    def __init__ (self, master=None):
        tk.Frame.__init__(self, master)
        self.grid()
        self.createWidgets()

    def createWidgets (self):
        self.quitButton = tk.Button(self, text='Quit', command=self.quit)
        self.quitButton.grid()
        self.console = tkinter.Text(self)
        self.console.config(state=tkinter.DISABLED)
        self.console.grid()

    def startProcess (self):
        dir = "C:/folder/"
        self.process = subprocess.Popen([ "python", "-u", dir + "start.py" ], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dir)
        self.updateLines()

    def updateLines (self):
        self.console.config(state=tkinter.NORMAL)
        while True:
            line = self.process.stdout.readline().decode().rstrip()
            if line == '' and self.process.poll() != None:
                break
            else: 
                self.console.insert(tkinter.END, line + "\n")
        self.console.config(state=tkinter.DISABLED)
        self.after(1, self.updateLines)

app = Application()
app.startProcess()
app.mainloop()

另外,如果写得不好,请随意破坏我的代码.这是我的第一个python项目,我不希望对该语言有任何好处.

解决方法:

这里的问题是process.stdout.readline()将阻塞,直到有一整行可用.这意味着在进程退出之前,将永远不会满足条件行==”.你有两个选择.

首先,您可以将stdout设置为非阻塞并自行管理缓冲区.它看起来像这样.编辑:正如Terry Jan Reedy所指出的那样,这只是一个Unix解决方案.第二种选择应该是首选.

import fcntl
...

    def startProcess(self):
        self.process = subprocess.Popen(['./subtest.sh'],
            stdout=subprocess.PIPE,
            stdin=subprocess.PIPE,
            stderr=subprocess.PIPE,
            bufsize=0) # prevent any unnecessary buffering

        # set stdout to non-blocking
        fd = self.process.stdout.fileno()
        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

        # schedule updatelines
        self.after(100, self.updateLines)

    def updateLines(self):
        # read stdout as much as we can
        line = ''
        while True:
            buff = self.process.stdout.read(1024)
            if buff:
                buff += line.decode()
            else:
                break

        self.console.config(state=tkinter.NORMAL)
        self.console.insert(tkinter.END, line)
        self.console.config(state=tkinter.DISABLED)

        # schedule callback
        if self.process.poll() is None:
            self.after(100, self.updateLines)

第二种方法是让一个单独的线程将行读入队列.然后从队列中弹出更新行.它看起来像这样

from threading import Thread
from queue import Queue, Empty

def readlines(process, queue):
    while process.poll() is None:
        queue.put(process.stdout.readline())
...

    def startProcess(self):
        self.process = subprocess.Popen(['./subtest.sh'],
            stdout=subprocess.PIPE,
            stdin=subprocess.PIPE,
            stderr=subprocess.PIPE)

        self.queue = Queue()
        self.thread = Thread(target=readlines, args=(self.process, self.queue))
        self.thread.start()

        self.after(100, self.updateLines)

    def updateLines(self):
        try:
            line = self.queue.get(False) # False for non-blocking, raises Empty if empty
            self.console.config(state=tkinter.NORMAL)
            self.console.insert(tkinter.END, line)
            self.console.config(state=tkinter.DISABLED)
        except Empty:
            pass

        if self.process.poll() is None:
            self.after(100, self.updateLines)

穿线路线可能更安全.我不肯定将stdout设置为非阻塞将适用于所有平台.

上一篇:将pandas数据框传递到python子进程中.Popen作为参数


下一篇:使用Python打开Shell环境,运行命令并退出环境