Python 多处理将子进程的标准输出重定向到 Tkinter 文本

2022-01-12 00:00:00 python tkinter multiprocessing

问题描述

我正在尝试使用 Tkinter GUI 来启动子进程并将其 stdout/stderr 输出显示到 Text 小部件.最初,我认为 sys.stdout 可以通过设置sys.stdout = text_widget"轻松重定向到文本小部件,但似乎不是.报错:Text instance has no attribute 'flush'".

I'm trying to use Tkinter GUI to launch a child process and display it stdout/stderr output to a Text widget. Initially, I thought the sys.stdout can be easily redirected to the Text widget by setting "sys.stdout = text_widget" but seems not. It comes to an error: "Text instance has no attribute 'flush'".

我在网上查了一下,得到了一些解决方案,比如使用队列与子进程通信.但是,由于我的特殊要求,它们都不适合我的情况:

I checked online and got some solutions, like using a Queue to communicate with the child process. However, none of them fit my case because of my special requirement:

  1. 子进程最好由multiprocessing.Process"启动,因为它更容易使用共享变量,这使得子进程解决方案可用.
  2. 子进程的代码已经存在,里面有很多打印",所以我不想将它们修改为Queue.put()"之类的东西.

在这种情况下,任何人都可以找到获得multiprocessing.Process"的打印"输出并显示到 Tkinter Text 的解决方案吗?非常感谢!

In this case, could anyone come to a solution of getting a "multiprocessing.Process"'s "print" output and display to a Tkinter Text? Many thanks!

我的案例的示例代码如下:

An example code of my case is a follows:

import sys
import time
from multiprocessing import Process
from Tkinter import *

def test_child():
    print 'child running'

def test_parent():
    print 'parent running'
    time.sleep(0.5)
    Process(target=test_child).start()

def set_txt(msg):
    gui_txt.insert(END, str(msg))
    gui_txt.see(END)

if __name__ == '__main__':
    gui_root = Tk()
    gui_txt = Text(gui_root)
    gui_txt.pack()
    gui_btn = Button(gui_root, text='Test', command=test_parent)
    gui_btn.pack()

    gui_txt.write = set_txt
    sys.stdout = gui_txt

    gui_root.mainloop()


解决方案

仍然可以使用队列,而不必摆脱所有 print 语句.您可以使用 Process 依赖 stdout 重定向来执行此操作.下面的解决方案使用 Queue 子类来模仿 stdout.然后,该队列由一个线程监视,该线程会寻找被注入到文本小部件中的新文本.

It is still possible to use queues without having to get rid of all of your print statements. You can use a Process dependent stdout redirect to do this. The solution below uses a Queue subclass to mimic stdout. That queue is then monitored by a thread that looks for new text that gets pumped into the text widget.

import sys
import time
from multiprocessing import Process
from multiprocessing.queues import Queue
from threading import Thread
from Tkinter import *

# This function takes the text widget and a queue as inputs.
# It functions by waiting on new data entering the queue, when it 
# finds new data it will insert it into the text widget 
def text_catcher(text_widget,queue):
    while True:
        text_widget.insert(END, queue.get())

# This is a Queue that behaves like stdout
class StdoutQueue(Queue):
    def __init__(self,*args,**kwargs):
        Queue.__init__(self,*args,**kwargs)

    def write(self,msg):
        self.put(msg)

    def flush(self):
        sys.__stdout__.flush()


def test_child(q):
    # This line only redirects stdout inside the current process 
    sys.stdout = q
    # or sys.stdout = sys.__stdout__ if you want to print the child to the terminal
    print 'child running'

def test_parent(q):
    # Again this only redirects inside the current (main) process
    # commenting this like out will cause only the child to write to the widget 
    sys.stdout = q                                                                                                                                                                                                                                                         
    print 'parent running'
    time.sleep(0.5)
    Process(target=test_child,args=(q,)).start()

if __name__ == '__main__':
    gui_root = Tk()
    gui_txt = Text(gui_root)
    gui_txt.pack()
    q = StdoutQueue()
    gui_btn = Button(gui_root, text='Test', command=lambda:test_parent(q),)
    gui_btn.pack()

    # Instantiate and start the text monitor
    monitor = Thread(target=text_catcher,args=(gui_txt,q))
    monitor.daemon = True
    monitor.start()

    gui_root.mainloop()

相关文章